Code

8059202b919e31b85e4aca248f8065bca4d604f6
[tig.git] / tig.c
1 /* Copyright (c) 2006-2008 Jonas Fonseca <fonseca@diku.dk>
2  *
3  * This program is free software; you can redistribute it and/or
4  * modify it under the terms of the GNU General Public License as
5  * published by the Free Software Foundation; either version 2 of
6  * the License, or (at your option) any later version.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11  * GNU General Public License for more details.
12  */
14 #ifdef HAVE_CONFIG_H
15 #include "config.h"
16 #endif
18 #ifndef TIG_VERSION
19 #define TIG_VERSION "unknown-version"
20 #endif
22 #ifndef DEBUG
23 #define NDEBUG
24 #endif
26 #include <assert.h>
27 #include <errno.h>
28 #include <ctype.h>
29 #include <signal.h>
30 #include <stdarg.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <sys/types.h>
35 #include <sys/stat.h>
36 #include <unistd.h>
37 #include <time.h>
39 #include <regex.h>
41 #include <locale.h>
42 #include <langinfo.h>
43 #include <iconv.h>
45 /* ncurses(3): Must be defined to have extended wide-character functions. */
46 #define _XOPEN_SOURCE_EXTENDED
48 #ifdef HAVE_NCURSESW_NCURSES_H
49 #include <ncursesw/ncurses.h>
50 #else
51 #ifdef HAVE_NCURSES_NCURSES_H
52 #include <ncurses/ncurses.h>
53 #else
54 #include <ncurses.h>
55 #endif
56 #endif
58 #if __GNUC__ >= 3
59 #define __NORETURN __attribute__((__noreturn__))
60 #else
61 #define __NORETURN
62 #endif
64 static void __NORETURN die(const char *err, ...);
65 static void warn(const char *msg, ...);
66 static void report(const char *msg, ...);
67 static int read_properties(FILE *pipe, const char *separators, int (*read)(char *, size_t, char *, size_t));
68 static void set_nonblocking_input(bool loading);
69 static size_t utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve);
70 static bool prompt_yesno(const char *prompt);
71 static int load_refs(void);
73 #define ABS(x)          ((x) >= 0  ? (x) : -(x))
74 #define MIN(x, y)       ((x) < (y) ? (x) :  (y))
76 #define ARRAY_SIZE(x)   (sizeof(x) / sizeof(x[0]))
77 #define STRING_SIZE(x)  (sizeof(x) - 1)
79 #define SIZEOF_STR      1024    /* Default string size. */
80 #define SIZEOF_REF      256     /* Size of symbolic or SHA1 ID. */
81 #define SIZEOF_REV      41      /* Holds a SHA-1 and an ending NUL. */
82 #define SIZEOF_ARG      32      /* Default argument array size. */
84 /* Revision graph */
86 #define REVGRAPH_INIT   'I'
87 #define REVGRAPH_MERGE  'M'
88 #define REVGRAPH_BRANCH '+'
89 #define REVGRAPH_COMMIT '*'
90 #define REVGRAPH_BOUND  '^'
92 #define SIZEOF_REVGRAPH 19      /* Size of revision ancestry graphics. */
94 /* This color name can be used to refer to the default term colors. */
95 #define COLOR_DEFAULT   (-1)
97 #define ICONV_NONE      ((iconv_t) -1)
98 #ifndef ICONV_CONST
99 #define ICONV_CONST     /* nothing */
100 #endif
102 /* The format and size of the date column in the main view. */
103 #define DATE_FORMAT     "%Y-%m-%d %H:%M"
104 #define DATE_COLS       STRING_SIZE("2006-04-29 14:21 ")
106 #define AUTHOR_COLS     20
107 #define ID_COLS         8
109 /* The default interval between line numbers. */
110 #define NUMBER_INTERVAL 5
112 #define TAB_SIZE        8
114 #define SCALE_SPLIT_VIEW(height)        ((height) * 2 / 3)
116 #define NULL_ID         "0000000000000000000000000000000000000000"
118 #ifndef GIT_CONFIG
119 #define GIT_CONFIG "config"
120 #endif
122 #define TIG_LS_REMOTE \
123         "git ls-remote . 2>/dev/null"
125 #define TIG_DIFF_CMD \
126         "git show --pretty=fuller --no-color --root --patch-with-stat --find-copies-harder -C %s 2>/dev/null"
128 #define TIG_LOG_CMD     \
129         "git log --no-color --cc --stat -n100 %s 2>/dev/null"
131 #define TIG_MAIN_BASE \
132         "git log --no-color --pretty=raw --parents --topo-order"
134 #define TIG_MAIN_CMD \
135         TIG_MAIN_BASE " %s 2>/dev/null"
137 #define TIG_TREE_CMD    \
138         "git ls-tree %s %s"
140 #define TIG_BLOB_CMD    \
141         "git cat-file blob %s"
143 /* XXX: Needs to be defined to the empty string. */
144 #define TIG_HELP_CMD    ""
145 #define TIG_PAGER_CMD   ""
146 #define TIG_STATUS_CMD  ""
147 #define TIG_STAGE_CMD   ""
148 #define TIG_BLAME_CMD   ""
150 /* Some ascii-shorthands fitted into the ncurses namespace. */
151 #define KEY_TAB         '\t'
152 #define KEY_RETURN      '\r'
153 #define KEY_ESC         27
156 struct ref {
157         char *name;             /* Ref name; tag or head names are shortened. */
158         char id[SIZEOF_REV];    /* Commit SHA1 ID */
159         unsigned int head:1;    /* Is it the current HEAD? */
160         unsigned int tag:1;     /* Is it a tag? */
161         unsigned int ltag:1;    /* If so, is the tag local? */
162         unsigned int remote:1;  /* Is it a remote ref? */
163         unsigned int tracked:1; /* Is it the remote for the current HEAD? */
164         unsigned int next:1;    /* For ref lists: are there more refs? */
165 };
167 static struct ref **get_refs(const char *id);
169 struct int_map {
170         const char *name;
171         int namelen;
172         int value;
173 };
175 static int
176 set_from_int_map(struct int_map *map, size_t map_size,
177                  int *value, const char *name, int namelen)
180         int i;
182         for (i = 0; i < map_size; i++)
183                 if (namelen == map[i].namelen &&
184                     !strncasecmp(name, map[i].name, namelen)) {
185                         *value = map[i].value;
186                         return OK;
187                 }
189         return ERR;
193 /*
194  * String helpers
195  */
197 static inline void
198 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
200         if (srclen > dstlen - 1)
201                 srclen = dstlen - 1;
203         strncpy(dst, src, srclen);
204         dst[srclen] = 0;
207 /* Shorthands for safely copying into a fixed buffer. */
209 #define string_copy(dst, src) \
210         string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
212 #define string_ncopy(dst, src, srclen) \
213         string_ncopy_do(dst, sizeof(dst), src, srclen)
215 #define string_copy_rev(dst, src) \
216         string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
218 #define string_add(dst, from, src) \
219         string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
221 static char *
222 chomp_string(char *name)
224         int namelen;
226         while (isspace(*name))
227                 name++;
229         namelen = strlen(name) - 1;
230         while (namelen > 0 && isspace(name[namelen]))
231                 name[namelen--] = 0;
233         return name;
236 static bool
237 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
239         va_list args;
240         size_t pos = bufpos ? *bufpos : 0;
242         va_start(args, fmt);
243         pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
244         va_end(args);
246         if (bufpos)
247                 *bufpos = pos;
249         return pos >= bufsize ? FALSE : TRUE;
252 #define string_format(buf, fmt, args...) \
253         string_nformat(buf, sizeof(buf), NULL, fmt, args)
255 #define string_format_from(buf, from, fmt, args...) \
256         string_nformat(buf, sizeof(buf), from, fmt, args)
258 static int
259 string_enum_compare(const char *str1, const char *str2, int len)
261         size_t i;
263 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
265         /* Diff-Header == DIFF_HEADER */
266         for (i = 0; i < len; i++) {
267                 if (toupper(str1[i]) == toupper(str2[i]))
268                         continue;
270                 if (string_enum_sep(str1[i]) &&
271                     string_enum_sep(str2[i]))
272                         continue;
274                 return str1[i] - str2[i];
275         }
277         return 0;
280 #define prefixcmp(str1, str2) \
281         strncmp(str1, str2, STRING_SIZE(str2))
283 static inline int
284 suffixcmp(const char *str, int slen, const char *suffix)
286         size_t len = slen >= 0 ? slen : strlen(str);
287         size_t suffixlen = strlen(suffix);
289         return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
292 /* Shell quoting
293  *
294  * NOTE: The following is a slightly modified copy of the git project's shell
295  * quoting routines found in the quote.c file.
296  *
297  * Help to copy the thing properly quoted for the shell safety.  any single
298  * quote is replaced with '\'', any exclamation point is replaced with '\!',
299  * and the whole thing is enclosed in a
300  *
301  * E.g.
302  *  original     sq_quote     result
303  *  name     ==> name      ==> 'name'
304  *  a b      ==> a b       ==> 'a b'
305  *  a'b      ==> a'\''b    ==> 'a'\''b'
306  *  a!b      ==> a'\!'b    ==> 'a'\!'b'
307  */
309 static size_t
310 sq_quote(char buf[SIZEOF_STR], size_t bufsize, const char *src)
312         char c;
314 #define BUFPUT(x) do { if (bufsize < SIZEOF_STR) buf[bufsize++] = (x); } while (0)
316         BUFPUT('\'');
317         while ((c = *src++)) {
318                 if (c == '\'' || c == '!') {
319                         BUFPUT('\'');
320                         BUFPUT('\\');
321                         BUFPUT(c);
322                         BUFPUT('\'');
323                 } else {
324                         BUFPUT(c);
325                 }
326         }
327         BUFPUT('\'');
329         if (bufsize < SIZEOF_STR)
330                 buf[bufsize] = 0;
332         return bufsize;
336 /*
337  * User requests
338  */
340 #define REQ_INFO \
341         /* XXX: Keep the view request first and in sync with views[]. */ \
342         REQ_GROUP("View switching") \
343         REQ_(VIEW_MAIN,         "Show main view"), \
344         REQ_(VIEW_DIFF,         "Show diff view"), \
345         REQ_(VIEW_LOG,          "Show log view"), \
346         REQ_(VIEW_TREE,         "Show tree view"), \
347         REQ_(VIEW_BLOB,         "Show blob view"), \
348         REQ_(VIEW_BLAME,        "Show blame view"), \
349         REQ_(VIEW_HELP,         "Show help page"), \
350         REQ_(VIEW_PAGER,        "Show pager view"), \
351         REQ_(VIEW_STATUS,       "Show status view"), \
352         REQ_(VIEW_STAGE,        "Show stage view"), \
353         \
354         REQ_GROUP("View manipulation") \
355         REQ_(ENTER,             "Enter current line and scroll"), \
356         REQ_(NEXT,              "Move to next"), \
357         REQ_(PREVIOUS,          "Move to previous"), \
358         REQ_(VIEW_NEXT,         "Move focus to next view"), \
359         REQ_(REFRESH,           "Reload and refresh"), \
360         REQ_(MAXIMIZE,          "Maximize the current view"), \
361         REQ_(VIEW_CLOSE,        "Close the current view"), \
362         REQ_(QUIT,              "Close all views and quit"), \
363         \
364         REQ_GROUP("View specific requests") \
365         REQ_(STATUS_UPDATE,     "Update file status"), \
366         REQ_(STATUS_REVERT,     "Revert file changes"), \
367         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
368         REQ_(STAGE_NEXT,        "Find next chunk to stage"), \
369         REQ_(TREE_PARENT,       "Switch to parent directory in tree view"), \
370         \
371         REQ_GROUP("Cursor navigation") \
372         REQ_(MOVE_UP,           "Move cursor one line up"), \
373         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
374         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
375         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
376         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
377         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
378         \
379         REQ_GROUP("Scrolling") \
380         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
381         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
382         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
383         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
384         \
385         REQ_GROUP("Searching") \
386         REQ_(SEARCH,            "Search the view"), \
387         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
388         REQ_(FIND_NEXT,         "Find next search match"), \
389         REQ_(FIND_PREV,         "Find previous search match"), \
390         \
391         REQ_GROUP("Option manipulation") \
392         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
393         REQ_(TOGGLE_DATE,       "Toggle date display"), \
394         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
395         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
396         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
397         \
398         REQ_GROUP("Misc") \
399         REQ_(PROMPT,            "Bring up the prompt"), \
400         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
401         REQ_(SCREEN_RESIZE,     "Resize the screen"), \
402         REQ_(SHOW_VERSION,      "Show version information"), \
403         REQ_(STOP_LOADING,      "Stop all loading views"), \
404         REQ_(EDIT,              "Open in editor"), \
405         REQ_(NONE,              "Do nothing")
408 /* User action requests. */
409 enum request {
410 #define REQ_GROUP(help)
411 #define REQ_(req, help) REQ_##req
413         /* Offset all requests to avoid conflicts with ncurses getch values. */
414         REQ_OFFSET = KEY_MAX + 1,
415         REQ_INFO
417 #undef  REQ_GROUP
418 #undef  REQ_
419 };
421 struct request_info {
422         enum request request;
423         const char *name;
424         int namelen;
425         const char *help;
426 };
428 static struct request_info req_info[] = {
429 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
430 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
431         REQ_INFO
432 #undef  REQ_GROUP
433 #undef  REQ_
434 };
436 static enum request
437 get_request(const char *name)
439         int namelen = strlen(name);
440         int i;
442         for (i = 0; i < ARRAY_SIZE(req_info); i++)
443                 if (req_info[i].namelen == namelen &&
444                     !string_enum_compare(req_info[i].name, name, namelen))
445                         return req_info[i].request;
447         return REQ_NONE;
451 /*
452  * Options
453  */
455 static const char usage[] =
456 "tig " TIG_VERSION " (" __DATE__ ")\n"
457 "\n"
458 "Usage: tig        [options] [revs] [--] [paths]\n"
459 "   or: tig show   [options] [revs] [--] [paths]\n"
460 "   or: tig blame  [rev] path\n"
461 "   or: tig status\n"
462 "   or: tig <      [git command output]\n"
463 "\n"
464 "Options:\n"
465 "  -v, --version   Show version and exit\n"
466 "  -h, --help      Show help message and exit";
468 /* Option and state variables. */
469 static bool opt_date                    = TRUE;
470 static bool opt_author                  = TRUE;
471 static bool opt_line_number             = FALSE;
472 static bool opt_line_graphics           = TRUE;
473 static bool opt_rev_graph               = FALSE;
474 static bool opt_show_refs               = TRUE;
475 static int opt_num_interval             = NUMBER_INTERVAL;
476 static int opt_tab_size                 = TAB_SIZE;
477 static int opt_author_cols              = AUTHOR_COLS-1;
478 static char opt_cmd[SIZEOF_STR]         = "";
479 static char opt_path[SIZEOF_STR]        = "";
480 static char opt_file[SIZEOF_STR]        = "";
481 static char opt_ref[SIZEOF_REF]         = "";
482 static char opt_head[SIZEOF_REF]        = "";
483 static char opt_remote[SIZEOF_REF]      = "";
484 static bool opt_no_head                 = TRUE;
485 static FILE *opt_pipe                   = NULL;
486 static char opt_encoding[20]            = "UTF-8";
487 static bool opt_utf8                    = TRUE;
488 static char opt_codeset[20]             = "UTF-8";
489 static iconv_t opt_iconv                = ICONV_NONE;
490 static char opt_search[SIZEOF_STR]      = "";
491 static char opt_cdup[SIZEOF_STR]        = "";
492 static char opt_git_dir[SIZEOF_STR]     = "";
493 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
494 static char opt_editor[SIZEOF_STR]      = "";
495 static FILE *opt_tty                    = NULL;
497 static enum request
498 parse_options(int argc, const char *argv[])
500         enum request request = REQ_VIEW_MAIN;
501         size_t buf_size;
502         const char *subcommand;
503         bool seen_dashdash = FALSE;
504         int i;
506         if (!isatty(STDIN_FILENO)) {
507                 opt_pipe = stdin;
508                 return REQ_VIEW_PAGER;
509         }
511         if (argc <= 1)
512                 return REQ_VIEW_MAIN;
514         subcommand = argv[1];
515         if (!strcmp(subcommand, "status") || !strcmp(subcommand, "-S")) {
516                 if (!strcmp(subcommand, "-S"))
517                         warn("`-S' has been deprecated; use `tig status' instead");
518                 if (argc > 2)
519                         warn("ignoring arguments after `%s'", subcommand);
520                 return REQ_VIEW_STATUS;
522         } else if (!strcmp(subcommand, "blame")) {
523                 if (argc <= 2 || argc > 4)
524                         die("invalid number of options to blame\n\n%s", usage);
526                 i = 2;
527                 if (argc == 4) {
528                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
529                         i++;
530                 }
532                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
533                 return REQ_VIEW_BLAME;
535         } else if (!strcmp(subcommand, "show")) {
536                 request = REQ_VIEW_DIFF;
538         } else if (!strcmp(subcommand, "log") || !strcmp(subcommand, "diff")) {
539                 request = subcommand[0] == 'l' ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
540                 warn("`tig %s' has been deprecated", subcommand);
542         } else {
543                 subcommand = NULL;
544         }
546         if (!subcommand)
547                 /* XXX: This is vulnerable to the user overriding
548                  * options required for the main view parser. */
549                 string_copy(opt_cmd, TIG_MAIN_BASE);
550         else
551                 string_format(opt_cmd, "git %s", subcommand);
553         buf_size = strlen(opt_cmd);
555         for (i = 1 + !!subcommand; i < argc; i++) {
556                 const char *opt = argv[i];
558                 if (seen_dashdash || !strcmp(opt, "--")) {
559                         seen_dashdash = TRUE;
561                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
562                         printf("tig version %s\n", TIG_VERSION);
563                         return REQ_NONE;
565                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
566                         printf("%s\n", usage);
567                         return REQ_NONE;
568                 }
570                 opt_cmd[buf_size++] = ' ';
571                 buf_size = sq_quote(opt_cmd, buf_size, opt);
572                 if (buf_size >= sizeof(opt_cmd))
573                         die("command too long");
574         }
576         opt_cmd[buf_size] = 0;
578         return request;
582 /*
583  * Line-oriented content detection.
584  */
586 #define LINE_INFO \
587 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
588 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
589 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
590 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
591 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
592 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
593 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
594 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
595 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
596 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
597 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
598 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
599 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
600 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
601 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
602 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
603 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
604 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
605 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
606 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
607 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
608 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
609 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
610 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
611 LINE(AUTHOR,       "author ",           COLOR_CYAN,     COLOR_DEFAULT,  0), \
612 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
613 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
614 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
615 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
616 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
617 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
618 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
619 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
620 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
621 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
622 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
623 LINE(MAIN_AUTHOR,  "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
624 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
625 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
626 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
627 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
628 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
629 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
630 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
631 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
632 LINE(TREE_DIR,     "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
633 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
634 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
635 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
636 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
637 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
638 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
639 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
640 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
642 enum line_type {
643 #define LINE(type, line, fg, bg, attr) \
644         LINE_##type
645         LINE_INFO,
646         LINE_NONE
647 #undef  LINE
648 };
650 struct line_info {
651         const char *name;       /* Option name. */
652         int namelen;            /* Size of option name. */
653         const char *line;       /* The start of line to match. */
654         int linelen;            /* Size of string to match. */
655         int fg, bg, attr;       /* Color and text attributes for the lines. */
656 };
658 static struct line_info line_info[] = {
659 #define LINE(type, line, fg, bg, attr) \
660         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
661         LINE_INFO
662 #undef  LINE
663 };
665 static enum line_type
666 get_line_type(const char *line)
668         int linelen = strlen(line);
669         enum line_type type;
671         for (type = 0; type < ARRAY_SIZE(line_info); type++)
672                 /* Case insensitive search matches Signed-off-by lines better. */
673                 if (linelen >= line_info[type].linelen &&
674                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
675                         return type;
677         return LINE_DEFAULT;
680 static inline int
681 get_line_attr(enum line_type type)
683         assert(type < ARRAY_SIZE(line_info));
684         return COLOR_PAIR(type) | line_info[type].attr;
687 static struct line_info *
688 get_line_info(const char *name)
690         size_t namelen = strlen(name);
691         enum line_type type;
693         for (type = 0; type < ARRAY_SIZE(line_info); type++)
694                 if (namelen == line_info[type].namelen &&
695                     !string_enum_compare(line_info[type].name, name, namelen))
696                         return &line_info[type];
698         return NULL;
701 static void
702 init_colors(void)
704         int default_bg = line_info[LINE_DEFAULT].bg;
705         int default_fg = line_info[LINE_DEFAULT].fg;
706         enum line_type type;
708         start_color();
710         if (assume_default_colors(default_fg, default_bg) == ERR) {
711                 default_bg = COLOR_BLACK;
712                 default_fg = COLOR_WHITE;
713         }
715         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
716                 struct line_info *info = &line_info[type];
717                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
718                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
720                 init_pair(type, fg, bg);
721         }
724 struct line {
725         enum line_type type;
727         /* State flags */
728         unsigned int selected:1;
729         unsigned int dirty:1;
731         void *data;             /* User data */
732 };
735 /*
736  * Keys
737  */
739 struct keybinding {
740         int alias;
741         enum request request;
742         struct keybinding *next;
743 };
745 static struct keybinding default_keybindings[] = {
746         /* View switching */
747         { 'm',          REQ_VIEW_MAIN },
748         { 'd',          REQ_VIEW_DIFF },
749         { 'l',          REQ_VIEW_LOG },
750         { 't',          REQ_VIEW_TREE },
751         { 'f',          REQ_VIEW_BLOB },
752         { 'B',          REQ_VIEW_BLAME },
753         { 'p',          REQ_VIEW_PAGER },
754         { 'h',          REQ_VIEW_HELP },
755         { 'S',          REQ_VIEW_STATUS },
756         { 'c',          REQ_VIEW_STAGE },
758         /* View manipulation */
759         { 'q',          REQ_VIEW_CLOSE },
760         { KEY_TAB,      REQ_VIEW_NEXT },
761         { KEY_RETURN,   REQ_ENTER },
762         { KEY_UP,       REQ_PREVIOUS },
763         { KEY_DOWN,     REQ_NEXT },
764         { 'R',          REQ_REFRESH },
765         { KEY_F(5),     REQ_REFRESH },
766         { 'O',          REQ_MAXIMIZE },
768         /* Cursor navigation */
769         { 'k',          REQ_MOVE_UP },
770         { 'j',          REQ_MOVE_DOWN },
771         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
772         { KEY_END,      REQ_MOVE_LAST_LINE },
773         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
774         { ' ',          REQ_MOVE_PAGE_DOWN },
775         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
776         { 'b',          REQ_MOVE_PAGE_UP },
777         { '-',          REQ_MOVE_PAGE_UP },
779         /* Scrolling */
780         { KEY_IC,       REQ_SCROLL_LINE_UP },
781         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
782         { 'w',          REQ_SCROLL_PAGE_UP },
783         { 's',          REQ_SCROLL_PAGE_DOWN },
785         /* Searching */
786         { '/',          REQ_SEARCH },
787         { '?',          REQ_SEARCH_BACK },
788         { 'n',          REQ_FIND_NEXT },
789         { 'N',          REQ_FIND_PREV },
791         /* Misc */
792         { 'Q',          REQ_QUIT },
793         { 'z',          REQ_STOP_LOADING },
794         { 'v',          REQ_SHOW_VERSION },
795         { 'r',          REQ_SCREEN_REDRAW },
796         { '.',          REQ_TOGGLE_LINENO },
797         { 'D',          REQ_TOGGLE_DATE },
798         { 'A',          REQ_TOGGLE_AUTHOR },
799         { 'g',          REQ_TOGGLE_REV_GRAPH },
800         { 'F',          REQ_TOGGLE_REFS },
801         { ':',          REQ_PROMPT },
802         { 'u',          REQ_STATUS_UPDATE },
803         { '!',          REQ_STATUS_REVERT },
804         { 'M',          REQ_STATUS_MERGE },
805         { '@',          REQ_STAGE_NEXT },
806         { ',',          REQ_TREE_PARENT },
807         { 'e',          REQ_EDIT },
809         /* Using the ncurses SIGWINCH handler. */
810         { KEY_RESIZE,   REQ_SCREEN_RESIZE },
811 };
813 #define KEYMAP_INFO \
814         KEYMAP_(GENERIC), \
815         KEYMAP_(MAIN), \
816         KEYMAP_(DIFF), \
817         KEYMAP_(LOG), \
818         KEYMAP_(TREE), \
819         KEYMAP_(BLOB), \
820         KEYMAP_(BLAME), \
821         KEYMAP_(PAGER), \
822         KEYMAP_(HELP), \
823         KEYMAP_(STATUS), \
824         KEYMAP_(STAGE)
826 enum keymap {
827 #define KEYMAP_(name) KEYMAP_##name
828         KEYMAP_INFO
829 #undef  KEYMAP_
830 };
832 static struct int_map keymap_table[] = {
833 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
834         KEYMAP_INFO
835 #undef  KEYMAP_
836 };
838 #define set_keymap(map, name) \
839         set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
841 static struct keybinding *keybindings[ARRAY_SIZE(keymap_table)];
843 static void
844 add_keybinding(enum keymap keymap, enum request request, int key)
846         struct keybinding *keybinding;
848         keybinding = calloc(1, sizeof(*keybinding));
849         if (!keybinding)
850                 die("Failed to allocate keybinding");
852         keybinding->alias = key;
853         keybinding->request = request;
854         keybinding->next = keybindings[keymap];
855         keybindings[keymap] = keybinding;
858 /* Looks for a key binding first in the given map, then in the generic map, and
859  * lastly in the default keybindings. */
860 static enum request
861 get_keybinding(enum keymap keymap, int key)
863         struct keybinding *kbd;
864         int i;
866         for (kbd = keybindings[keymap]; kbd; kbd = kbd->next)
867                 if (kbd->alias == key)
868                         return kbd->request;
870         for (kbd = keybindings[KEYMAP_GENERIC]; kbd; kbd = kbd->next)
871                 if (kbd->alias == key)
872                         return kbd->request;
874         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
875                 if (default_keybindings[i].alias == key)
876                         return default_keybindings[i].request;
878         return (enum request) key;
882 struct key {
883         const char *name;
884         int value;
885 };
887 static struct key key_table[] = {
888         { "Enter",      KEY_RETURN },
889         { "Space",      ' ' },
890         { "Backspace",  KEY_BACKSPACE },
891         { "Tab",        KEY_TAB },
892         { "Escape",     KEY_ESC },
893         { "Left",       KEY_LEFT },
894         { "Right",      KEY_RIGHT },
895         { "Up",         KEY_UP },
896         { "Down",       KEY_DOWN },
897         { "Insert",     KEY_IC },
898         { "Delete",     KEY_DC },
899         { "Hash",       '#' },
900         { "Home",       KEY_HOME },
901         { "End",        KEY_END },
902         { "PageUp",     KEY_PPAGE },
903         { "PageDown",   KEY_NPAGE },
904         { "F1",         KEY_F(1) },
905         { "F2",         KEY_F(2) },
906         { "F3",         KEY_F(3) },
907         { "F4",         KEY_F(4) },
908         { "F5",         KEY_F(5) },
909         { "F6",         KEY_F(6) },
910         { "F7",         KEY_F(7) },
911         { "F8",         KEY_F(8) },
912         { "F9",         KEY_F(9) },
913         { "F10",        KEY_F(10) },
914         { "F11",        KEY_F(11) },
915         { "F12",        KEY_F(12) },
916 };
918 static int
919 get_key_value(const char *name)
921         int i;
923         for (i = 0; i < ARRAY_SIZE(key_table); i++)
924                 if (!strcasecmp(key_table[i].name, name))
925                         return key_table[i].value;
927         if (strlen(name) == 1 && isprint(*name))
928                 return (int) *name;
930         return ERR;
933 static const char *
934 get_key_name(int key_value)
936         static char key_char[] = "'X'";
937         const char *seq = NULL;
938         int key;
940         for (key = 0; key < ARRAY_SIZE(key_table); key++)
941                 if (key_table[key].value == key_value)
942                         seq = key_table[key].name;
944         if (seq == NULL &&
945             key_value < 127 &&
946             isprint(key_value)) {
947                 key_char[1] = (char) key_value;
948                 seq = key_char;
949         }
951         return seq ? seq : "(no key)";
954 static const char *
955 get_key(enum request request)
957         static char buf[BUFSIZ];
958         size_t pos = 0;
959         char *sep = "";
960         int i;
962         buf[pos] = 0;
964         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
965                 struct keybinding *keybinding = &default_keybindings[i];
967                 if (keybinding->request != request)
968                         continue;
970                 if (!string_format_from(buf, &pos, "%s%s", sep,
971                                         get_key_name(keybinding->alias)))
972                         return "Too many keybindings!";
973                 sep = ", ";
974         }
976         return buf;
979 struct run_request {
980         enum keymap keymap;
981         int key;
982         char cmd[SIZEOF_STR];
983 };
985 static struct run_request *run_request;
986 static size_t run_requests;
988 static enum request
989 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
991         struct run_request *req;
992         char cmd[SIZEOF_STR];
993         size_t bufpos;
995         for (bufpos = 0; argc > 0; argc--, argv++)
996                 if (!string_format_from(cmd, &bufpos, "%s ", *argv))
997                         return REQ_NONE;
999         req = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
1000         if (!req)
1001                 return REQ_NONE;
1003         run_request = req;
1004         req = &run_request[run_requests++];
1005         string_copy(req->cmd, cmd);
1006         req->keymap = keymap;
1007         req->key = key;
1009         return REQ_NONE + run_requests;
1012 static struct run_request *
1013 get_run_request(enum request request)
1015         if (request <= REQ_NONE)
1016                 return NULL;
1017         return &run_request[request - REQ_NONE - 1];
1020 static void
1021 add_builtin_run_requests(void)
1023         struct {
1024                 enum keymap keymap;
1025                 int key;
1026                 const char *argv[1];
1027         } reqs[] = {
1028                 { KEYMAP_MAIN,    'C', { "git cherry-pick %(commit)" } },
1029                 { KEYMAP_GENERIC, 'G', { "git gc" } },
1030         };
1031         int i;
1033         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1034                 enum request req;
1036                 req = add_run_request(reqs[i].keymap, reqs[i].key, 1, reqs[i].argv);
1037                 if (req != REQ_NONE)
1038                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1039         }
1042 /*
1043  * User config file handling.
1044  */
1046 static struct int_map color_map[] = {
1047 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
1048         COLOR_MAP(DEFAULT),
1049         COLOR_MAP(BLACK),
1050         COLOR_MAP(BLUE),
1051         COLOR_MAP(CYAN),
1052         COLOR_MAP(GREEN),
1053         COLOR_MAP(MAGENTA),
1054         COLOR_MAP(RED),
1055         COLOR_MAP(WHITE),
1056         COLOR_MAP(YELLOW),
1057 };
1059 #define set_color(color, name) \
1060         set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
1062 static struct int_map attr_map[] = {
1063 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
1064         ATTR_MAP(NORMAL),
1065         ATTR_MAP(BLINK),
1066         ATTR_MAP(BOLD),
1067         ATTR_MAP(DIM),
1068         ATTR_MAP(REVERSE),
1069         ATTR_MAP(STANDOUT),
1070         ATTR_MAP(UNDERLINE),
1071 };
1073 #define set_attribute(attr, name) \
1074         set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
1076 static int   config_lineno;
1077 static bool  config_errors;
1078 static const char *config_msg;
1080 /* Wants: object fgcolor bgcolor [attr] */
1081 static int
1082 option_color_command(int argc, const char *argv[])
1084         struct line_info *info;
1086         if (argc != 3 && argc != 4) {
1087                 config_msg = "Wrong number of arguments given to color command";
1088                 return ERR;
1089         }
1091         info = get_line_info(argv[0]);
1092         if (!info) {
1093                 if (!string_enum_compare(argv[0], "main-delim", strlen("main-delim"))) {
1094                         info = get_line_info("delimiter");
1096                 } else if (!string_enum_compare(argv[0], "main-date", strlen("main-date"))) {
1097                         info = get_line_info("date");
1099                 } else {
1100                         config_msg = "Unknown color name";
1101                         return ERR;
1102                 }
1103         }
1105         if (set_color(&info->fg, argv[1]) == ERR ||
1106             set_color(&info->bg, argv[2]) == ERR) {
1107                 config_msg = "Unknown color";
1108                 return ERR;
1109         }
1111         if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1112                 config_msg = "Unknown attribute";
1113                 return ERR;
1114         }
1116         return OK;
1119 static bool parse_bool(const char *s)
1121         return (!strcmp(s, "1") || !strcmp(s, "true") ||
1122                 !strcmp(s, "yes")) ? TRUE : FALSE;
1125 static int
1126 parse_int(const char *s, int default_value, int min, int max)
1128         int value = atoi(s);
1130         return (value < min || value > max) ? default_value : value;
1133 /* Wants: name = value */
1134 static int
1135 option_set_command(int argc, const char *argv[])
1137         if (argc != 3) {
1138                 config_msg = "Wrong number of arguments given to set command";
1139                 return ERR;
1140         }
1142         if (strcmp(argv[1], "=")) {
1143                 config_msg = "No value assigned";
1144                 return ERR;
1145         }
1147         if (!strcmp(argv[0], "show-author")) {
1148                 opt_author = parse_bool(argv[2]);
1149                 return OK;
1150         }
1152         if (!strcmp(argv[0], "show-date")) {
1153                 opt_date = parse_bool(argv[2]);
1154                 return OK;
1155         }
1157         if (!strcmp(argv[0], "show-rev-graph")) {
1158                 opt_rev_graph = parse_bool(argv[2]);
1159                 return OK;
1160         }
1162         if (!strcmp(argv[0], "show-refs")) {
1163                 opt_show_refs = parse_bool(argv[2]);
1164                 return OK;
1165         }
1167         if (!strcmp(argv[0], "show-line-numbers")) {
1168                 opt_line_number = parse_bool(argv[2]);
1169                 return OK;
1170         }
1172         if (!strcmp(argv[0], "line-graphics")) {
1173                 opt_line_graphics = parse_bool(argv[2]);
1174                 return OK;
1175         }
1177         if (!strcmp(argv[0], "line-number-interval")) {
1178                 opt_num_interval = parse_int(argv[2], opt_num_interval, 1, 1024);
1179                 return OK;
1180         }
1182         if (!strcmp(argv[0], "author-width")) {
1183                 opt_author_cols = parse_int(argv[2], opt_author_cols, 0, 1024);
1184                 return OK;
1185         }
1187         if (!strcmp(argv[0], "tab-size")) {
1188                 opt_tab_size = parse_int(argv[2], opt_tab_size, 1, 1024);
1189                 return OK;
1190         }
1192         if (!strcmp(argv[0], "commit-encoding")) {
1193                 const char *arg = argv[2];
1194                 int arglen = strlen(arg);
1196                 switch (arg[0]) {
1197                 case '"':
1198                 case '\'':
1199                         if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1200                                 config_msg = "Unmatched quotation";
1201                                 return ERR;
1202                         }
1203                         arg += 1; arglen -= 2;
1204                 default:
1205                         string_ncopy(opt_encoding, arg, strlen(arg));
1206                         return OK;
1207                 }
1208         }
1210         config_msg = "Unknown variable name";
1211         return ERR;
1214 /* Wants: mode request key */
1215 static int
1216 option_bind_command(int argc, const char *argv[])
1218         enum request request;
1219         int keymap;
1220         int key;
1222         if (argc < 3) {
1223                 config_msg = "Wrong number of arguments given to bind command";
1224                 return ERR;
1225         }
1227         if (set_keymap(&keymap, argv[0]) == ERR) {
1228                 config_msg = "Unknown key map";
1229                 return ERR;
1230         }
1232         key = get_key_value(argv[1]);
1233         if (key == ERR) {
1234                 config_msg = "Unknown key";
1235                 return ERR;
1236         }
1238         request = get_request(argv[2]);
1239         if (request == REQ_NONE) {
1240                 const char *obsolete[] = { "cherry-pick" };
1241                 size_t namelen = strlen(argv[2]);
1242                 int i;
1244                 for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
1245                         if (namelen == strlen(obsolete[i]) &&
1246                             !string_enum_compare(obsolete[i], argv[2], namelen)) {
1247                                 config_msg = "Obsolete request name";
1248                                 return ERR;
1249                         }
1250                 }
1251         }
1252         if (request == REQ_NONE && *argv[2]++ == '!')
1253                 request = add_run_request(keymap, key, argc - 2, argv + 2);
1254         if (request == REQ_NONE) {
1255                 config_msg = "Unknown request name";
1256                 return ERR;
1257         }
1259         add_keybinding(keymap, request, key);
1261         return OK;
1264 static int
1265 set_option(const char *opt, char *value)
1267         const char *argv[SIZEOF_ARG];
1268         int valuelen;
1269         int argc = 0;
1271         /* Tokenize */
1272         while (argc < ARRAY_SIZE(argv) && (valuelen = strcspn(value, " \t"))) {
1273                 argv[argc++] = value;
1274                 value += valuelen;
1276                 /* Nothing more to tokenize or last available token. */
1277                 if (!*value || argc >= ARRAY_SIZE(argv))
1278                         break;
1280                 *value++ = 0;
1281                 while (isspace(*value))
1282                         value++;
1283         }
1285         if (!strcmp(opt, "color"))
1286                 return option_color_command(argc, argv);
1288         if (!strcmp(opt, "set"))
1289                 return option_set_command(argc, argv);
1291         if (!strcmp(opt, "bind"))
1292                 return option_bind_command(argc, argv);
1294         config_msg = "Unknown option command";
1295         return ERR;
1298 static int
1299 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1301         int status = OK;
1303         config_lineno++;
1304         config_msg = "Internal error";
1306         /* Check for comment markers, since read_properties() will
1307          * only ensure opt and value are split at first " \t". */
1308         optlen = strcspn(opt, "#");
1309         if (optlen == 0)
1310                 return OK;
1312         if (opt[optlen] != 0) {
1313                 config_msg = "No option value";
1314                 status = ERR;
1316         }  else {
1317                 /* Look for comment endings in the value. */
1318                 size_t len = strcspn(value, "#");
1320                 if (len < valuelen) {
1321                         valuelen = len;
1322                         value[valuelen] = 0;
1323                 }
1325                 status = set_option(opt, value);
1326         }
1328         if (status == ERR) {
1329                 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1330                         config_lineno, (int) optlen, opt, config_msg);
1331                 config_errors = TRUE;
1332         }
1334         /* Always keep going if errors are encountered. */
1335         return OK;
1338 static void
1339 load_option_file(const char *path)
1341         FILE *file;
1343         /* It's ok that the file doesn't exist. */
1344         file = fopen(path, "r");
1345         if (!file)
1346                 return;
1348         config_lineno = 0;
1349         config_errors = FALSE;
1351         if (read_properties(file, " \t", read_option) == ERR ||
1352             config_errors == TRUE)
1353                 fprintf(stderr, "Errors while loading %s.\n", path);
1356 static int
1357 load_options(void)
1359         const char *home = getenv("HOME");
1360         const char *tigrc_user = getenv("TIGRC_USER");
1361         const char *tigrc_system = getenv("TIGRC_SYSTEM");
1362         char buf[SIZEOF_STR];
1364         add_builtin_run_requests();
1366         if (!tigrc_system) {
1367                 if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
1368                         return ERR;
1369                 tigrc_system = buf;
1370         }
1371         load_option_file(tigrc_system);
1373         if (!tigrc_user) {
1374                 if (!home || !string_format(buf, "%s/.tigrc", home))
1375                         return ERR;
1376                 tigrc_user = buf;
1377         }
1378         load_option_file(tigrc_user);
1380         return OK;
1384 /*
1385  * The viewer
1386  */
1388 struct view;
1389 struct view_ops;
1391 /* The display array of active views and the index of the current view. */
1392 static struct view *display[2];
1393 static unsigned int current_view;
1395 /* Reading from the prompt? */
1396 static bool input_mode = FALSE;
1398 #define foreach_displayed_view(view, i) \
1399         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1401 #define displayed_views()       (display[1] != NULL ? 2 : 1)
1403 /* Current head and commit ID */
1404 static char ref_blob[SIZEOF_REF]        = "";
1405 static char ref_commit[SIZEOF_REF]      = "HEAD";
1406 static char ref_head[SIZEOF_REF]        = "HEAD";
1408 struct view {
1409         const char *name;       /* View name */
1410         const char *cmd_fmt;    /* Default command line format */
1411         const char *cmd_env;    /* Command line set via environment */
1412         const char *id;         /* Points to either of ref_{head,commit,blob} */
1414         struct view_ops *ops;   /* View operations */
1416         enum keymap keymap;     /* What keymap does this view have */
1417         bool git_dir;           /* Whether the view requires a git directory. */
1419         char cmd[SIZEOF_STR];   /* Command buffer */
1420         char ref[SIZEOF_REF];   /* Hovered commit reference */
1421         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
1423         int height, width;      /* The width and height of the main window */
1424         WINDOW *win;            /* The main window */
1425         WINDOW *title;          /* The title window living below the main window */
1427         /* Navigation */
1428         unsigned long offset;   /* Offset of the window top */
1429         unsigned long lineno;   /* Current line number */
1431         /* Searching */
1432         char grep[SIZEOF_STR];  /* Search string */
1433         regex_t *regex;         /* Pre-compiled regex */
1435         /* If non-NULL, points to the view that opened this view. If this view
1436          * is closed tig will switch back to the parent view. */
1437         struct view *parent;
1439         /* Buffering */
1440         size_t lines;           /* Total number of lines */
1441         struct line *line;      /* Line index */
1442         size_t line_alloc;      /* Total number of allocated lines */
1443         size_t line_size;       /* Total number of used lines */
1444         unsigned int digits;    /* Number of digits in the lines member. */
1446         /* Drawing */
1447         struct line *curline;   /* Line currently being drawn. */
1448         enum line_type curtype; /* Attribute currently used for drawing. */
1449         unsigned long col;      /* Column when drawing. */
1451         /* Loading */
1452         FILE *pipe;
1453         time_t start_time;
1454 };
1456 struct view_ops {
1457         /* What type of content being displayed. Used in the title bar. */
1458         const char *type;
1459         /* Open and reads in all view content. */
1460         bool (*open)(struct view *view);
1461         /* Read one line; updates view->line. */
1462         bool (*read)(struct view *view, char *data);
1463         /* Draw one line; @lineno must be < view->height. */
1464         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1465         /* Depending on view handle a special requests. */
1466         enum request (*request)(struct view *view, enum request request, struct line *line);
1467         /* Search for regex in a line. */
1468         bool (*grep)(struct view *view, struct line *line);
1469         /* Select line */
1470         void (*select)(struct view *view, struct line *line);
1471 };
1473 static struct view_ops blame_ops;
1474 static struct view_ops blob_ops;
1475 static struct view_ops help_ops;
1476 static struct view_ops log_ops;
1477 static struct view_ops main_ops;
1478 static struct view_ops pager_ops;
1479 static struct view_ops stage_ops;
1480 static struct view_ops status_ops;
1481 static struct view_ops tree_ops;
1483 #define VIEW_STR(name, cmd, env, ref, ops, map, git) \
1484         { name, cmd, #env, ref, ops, map, git }
1486 #define VIEW_(id, name, ops, git, ref) \
1487         VIEW_STR(name, TIG_##id##_CMD,  TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1490 static struct view views[] = {
1491         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
1492         VIEW_(DIFF,   "diff",   &pager_ops,  TRUE,  ref_commit),
1493         VIEW_(LOG,    "log",    &log_ops,    TRUE,  ref_head),
1494         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
1495         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
1496         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
1497         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
1498         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, "stdin"),
1499         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
1500         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
1501 };
1503 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
1504 #define VIEW_REQ(view)  ((view) - views + REQ_OFFSET + 1)
1506 #define foreach_view(view, i) \
1507         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1509 #define view_is_displayed(view) \
1510         (view == display[0] || view == display[1])
1513 enum line_graphic {
1514         LINE_GRAPHIC_VLINE
1515 };
1517 static int line_graphics[] = {
1518         /* LINE_GRAPHIC_VLINE: */ '|'
1519 };
1521 static inline void
1522 set_view_attr(struct view *view, enum line_type type)
1524         if (!view->curline->selected && view->curtype != type) {
1525                 wattrset(view->win, get_line_attr(type));
1526                 wchgat(view->win, -1, 0, type, NULL);
1527                 view->curtype = type;
1528         }
1531 static int
1532 draw_chars(struct view *view, enum line_type type, const char *string,
1533            int max_len, bool use_tilde)
1535         int len = 0;
1536         int col = 0;
1537         int trimmed = FALSE;
1539         if (max_len <= 0)
1540                 return 0;
1542         if (opt_utf8) {
1543                 len = utf8_length(string, &col, max_len, &trimmed, use_tilde);
1544         } else {
1545                 col = len = strlen(string);
1546                 if (len > max_len) {
1547                         if (use_tilde) {
1548                                 max_len -= 1;
1549                         }
1550                         col = len = max_len;
1551                         trimmed = TRUE;
1552                 }
1553         }
1555         set_view_attr(view, type);
1556         waddnstr(view->win, string, len);
1557         if (trimmed && use_tilde) {
1558                 set_view_attr(view, LINE_DELIMITER);
1559                 waddch(view->win, '~');
1560                 col++;
1561         }
1563         return col;
1566 static int
1567 draw_space(struct view *view, enum line_type type, int max, int spaces)
1569         static char space[] = "                    ";
1570         int col = 0;
1572         spaces = MIN(max, spaces);
1574         while (spaces > 0) {
1575                 int len = MIN(spaces, sizeof(space) - 1);
1577                 col += draw_chars(view, type, space, spaces, FALSE);
1578                 spaces -= len;
1579         }
1581         return col;
1584 static bool
1585 draw_lineno(struct view *view, unsigned int lineno)
1587         char number[10];
1588         int digits3 = view->digits < 3 ? 3 : view->digits;
1589         int max_number = MIN(digits3, STRING_SIZE(number));
1590         int max = view->width - view->col;
1591         int col;
1593         if (max < max_number)
1594                 max_number = max;
1596         lineno += view->offset + 1;
1597         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
1598                 static char fmt[] = "%1ld";
1600                 if (view->digits <= 9)
1601                         fmt[1] = '0' + digits3;
1603                 if (!string_format(number, fmt, lineno))
1604                         number[0] = 0;
1605                 col = draw_chars(view, LINE_LINE_NUMBER, number, max_number, TRUE);
1606         } else {
1607                 col = draw_space(view, LINE_LINE_NUMBER, max_number, max_number);
1608         }
1610         if (col < max) {
1611                 set_view_attr(view, LINE_DEFAULT);
1612                 waddch(view->win, line_graphics[LINE_GRAPHIC_VLINE]);
1613                 col++;
1614         }
1616         if (col < max)
1617                 col += draw_space(view, LINE_DEFAULT, max - col, 1);
1618         view->col += col;
1620         return view->width - view->col <= 0;
1623 static bool
1624 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1626         view->col += draw_chars(view, type, string, view->width - view->col, trim);
1627         return view->width - view->col <= 0;
1630 static bool
1631 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1633         int max = view->width - view->col;
1634         int i;
1636         if (max < size)
1637                 size = max;
1639         set_view_attr(view, type);
1640         /* Using waddch() instead of waddnstr() ensures that
1641          * they'll be rendered correctly for the cursor line. */
1642         for (i = 0; i < size; i++)
1643                 waddch(view->win, graphic[i]);
1645         view->col += size;
1646         if (size < max) {
1647                 waddch(view->win, ' ');
1648                 view->col++;
1649         }
1651         return view->width - view->col <= 0;
1654 static bool
1655 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
1657         int max = MIN(view->width - view->col, len);
1658         int col;
1660         if (text)
1661                 col = draw_chars(view, type, text, max - 1, trim);
1662         else
1663                 col = draw_space(view, type, max - 1, max - 1);
1665         view->col += col + draw_space(view, LINE_DEFAULT, max - col, max - col);
1666         return view->width - view->col <= 0;
1669 static bool
1670 draw_date(struct view *view, struct tm *time)
1672         char buf[DATE_COLS];
1673         char *date;
1674         int timelen = 0;
1676         if (time)
1677                 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
1678         date = timelen ? buf : NULL;
1680         return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
1683 static bool
1684 draw_view_line(struct view *view, unsigned int lineno)
1686         struct line *line;
1687         bool selected = (view->offset + lineno == view->lineno);
1688         bool draw_ok;
1690         assert(view_is_displayed(view));
1692         if (view->offset + lineno >= view->lines)
1693                 return FALSE;
1695         line = &view->line[view->offset + lineno];
1697         wmove(view->win, lineno, 0);
1698         view->col = 0;
1699         view->curline = line;
1700         view->curtype = LINE_NONE;
1701         line->selected = FALSE;
1703         if (selected) {
1704                 set_view_attr(view, LINE_CURSOR);
1705                 line->selected = TRUE;
1706                 view->ops->select(view, line);
1707         } else if (line->selected) {
1708                 wclrtoeol(view->win);
1709         }
1711         scrollok(view->win, FALSE);
1712         draw_ok = view->ops->draw(view, line, lineno);
1713         scrollok(view->win, TRUE);
1715         return draw_ok;
1718 static void
1719 redraw_view_dirty(struct view *view)
1721         bool dirty = FALSE;
1722         int lineno;
1724         for (lineno = 0; lineno < view->height; lineno++) {
1725                 struct line *line = &view->line[view->offset + lineno];
1727                 if (!line->dirty)
1728                         continue;
1729                 line->dirty = 0;
1730                 dirty = TRUE;
1731                 if (!draw_view_line(view, lineno))
1732                         break;
1733         }
1735         if (!dirty)
1736                 return;
1737         redrawwin(view->win);
1738         if (input_mode)
1739                 wnoutrefresh(view->win);
1740         else
1741                 wrefresh(view->win);
1744 static void
1745 redraw_view_from(struct view *view, int lineno)
1747         assert(0 <= lineno && lineno < view->height);
1749         for (; lineno < view->height; lineno++) {
1750                 if (!draw_view_line(view, lineno))
1751                         break;
1752         }
1754         redrawwin(view->win);
1755         if (input_mode)
1756                 wnoutrefresh(view->win);
1757         else
1758                 wrefresh(view->win);
1761 static void
1762 redraw_view(struct view *view)
1764         wclear(view->win);
1765         redraw_view_from(view, 0);
1769 static void
1770 update_view_title(struct view *view)
1772         char buf[SIZEOF_STR];
1773         char state[SIZEOF_STR];
1774         size_t bufpos = 0, statelen = 0;
1776         assert(view_is_displayed(view));
1778         if (view != VIEW(REQ_VIEW_STATUS) && (view->lines || view->pipe)) {
1779                 unsigned int view_lines = view->offset + view->height;
1780                 unsigned int lines = view->lines
1781                                    ? MIN(view_lines, view->lines) * 100 / view->lines
1782                                    : 0;
1784                 string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
1785                                    view->ops->type,
1786                                    view->lineno + 1,
1787                                    view->lines,
1788                                    lines);
1790                 if (view->pipe) {
1791                         time_t secs = time(NULL) - view->start_time;
1793                         /* Three git seconds are a long time ... */
1794                         if (secs > 2)
1795                                 string_format_from(state, &statelen, " %lds", secs);
1796                 }
1797         }
1799         string_format_from(buf, &bufpos, "[%s]", view->name);
1800         if (*view->ref && bufpos < view->width) {
1801                 size_t refsize = strlen(view->ref);
1802                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
1804                 if (minsize < view->width)
1805                         refsize = view->width - minsize + 7;
1806                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
1807         }
1809         if (statelen && bufpos < view->width) {
1810                 string_format_from(buf, &bufpos, " %s", state);
1811         }
1813         if (view == display[current_view])
1814                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
1815         else
1816                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
1818         mvwaddnstr(view->title, 0, 0, buf, bufpos);
1819         wclrtoeol(view->title);
1820         wmove(view->title, 0, view->width - 1);
1822         if (input_mode)
1823                 wnoutrefresh(view->title);
1824         else
1825                 wrefresh(view->title);
1828 static void
1829 resize_display(void)
1831         int offset, i;
1832         struct view *base = display[0];
1833         struct view *view = display[1] ? display[1] : display[0];
1835         /* Setup window dimensions */
1837         getmaxyx(stdscr, base->height, base->width);
1839         /* Make room for the status window. */
1840         base->height -= 1;
1842         if (view != base) {
1843                 /* Horizontal split. */
1844                 view->width   = base->width;
1845                 view->height  = SCALE_SPLIT_VIEW(base->height);
1846                 base->height -= view->height;
1848                 /* Make room for the title bar. */
1849                 view->height -= 1;
1850         }
1852         /* Make room for the title bar. */
1853         base->height -= 1;
1855         offset = 0;
1857         foreach_displayed_view (view, i) {
1858                 if (!view->win) {
1859                         view->win = newwin(view->height, 0, offset, 0);
1860                         if (!view->win)
1861                                 die("Failed to create %s view", view->name);
1863                         scrollok(view->win, TRUE);
1865                         view->title = newwin(1, 0, offset + view->height, 0);
1866                         if (!view->title)
1867                                 die("Failed to create title window");
1869                 } else {
1870                         wresize(view->win, view->height, view->width);
1871                         mvwin(view->win,   offset, 0);
1872                         mvwin(view->title, offset + view->height, 0);
1873                 }
1875                 offset += view->height + 1;
1876         }
1879 static void
1880 redraw_display(void)
1882         struct view *view;
1883         int i;
1885         foreach_displayed_view (view, i) {
1886                 redraw_view(view);
1887                 update_view_title(view);
1888         }
1891 static void
1892 update_display_cursor(struct view *view)
1894         /* Move the cursor to the right-most column of the cursor line.
1895          *
1896          * XXX: This could turn out to be a bit expensive, but it ensures that
1897          * the cursor does not jump around. */
1898         if (view->lines) {
1899                 wmove(view->win, view->lineno - view->offset, view->width - 1);
1900                 wrefresh(view->win);
1901         }
1904 /*
1905  * Navigation
1906  */
1908 /* Scrolling backend */
1909 static void
1910 do_scroll_view(struct view *view, int lines)
1912         bool redraw_current_line = FALSE;
1914         /* The rendering expects the new offset. */
1915         view->offset += lines;
1917         assert(0 <= view->offset && view->offset < view->lines);
1918         assert(lines);
1920         /* Move current line into the view. */
1921         if (view->lineno < view->offset) {
1922                 view->lineno = view->offset;
1923                 redraw_current_line = TRUE;
1924         } else if (view->lineno >= view->offset + view->height) {
1925                 view->lineno = view->offset + view->height - 1;
1926                 redraw_current_line = TRUE;
1927         }
1929         assert(view->offset <= view->lineno && view->lineno < view->lines);
1931         /* Redraw the whole screen if scrolling is pointless. */
1932         if (view->height < ABS(lines)) {
1933                 redraw_view(view);
1935         } else {
1936                 int line = lines > 0 ? view->height - lines : 0;
1937                 int end = line + ABS(lines);
1939                 wscrl(view->win, lines);
1941                 for (; line < end; line++) {
1942                         if (!draw_view_line(view, line))
1943                                 break;
1944                 }
1946                 if (redraw_current_line)
1947                         draw_view_line(view, view->lineno - view->offset);
1948         }
1950         redrawwin(view->win);
1951         wrefresh(view->win);
1952         report("");
1955 /* Scroll frontend */
1956 static void
1957 scroll_view(struct view *view, enum request request)
1959         int lines = 1;
1961         assert(view_is_displayed(view));
1963         switch (request) {
1964         case REQ_SCROLL_PAGE_DOWN:
1965                 lines = view->height;
1966         case REQ_SCROLL_LINE_DOWN:
1967                 if (view->offset + lines > view->lines)
1968                         lines = view->lines - view->offset;
1970                 if (lines == 0 || view->offset + view->height >= view->lines) {
1971                         report("Cannot scroll beyond the last line");
1972                         return;
1973                 }
1974                 break;
1976         case REQ_SCROLL_PAGE_UP:
1977                 lines = view->height;
1978         case REQ_SCROLL_LINE_UP:
1979                 if (lines > view->offset)
1980                         lines = view->offset;
1982                 if (lines == 0) {
1983                         report("Cannot scroll beyond the first line");
1984                         return;
1985                 }
1987                 lines = -lines;
1988                 break;
1990         default:
1991                 die("request %d not handled in switch", request);
1992         }
1994         do_scroll_view(view, lines);
1997 /* Cursor moving */
1998 static void
1999 move_view(struct view *view, enum request request)
2001         int scroll_steps = 0;
2002         int steps;
2004         switch (request) {
2005         case REQ_MOVE_FIRST_LINE:
2006                 steps = -view->lineno;
2007                 break;
2009         case REQ_MOVE_LAST_LINE:
2010                 steps = view->lines - view->lineno - 1;
2011                 break;
2013         case REQ_MOVE_PAGE_UP:
2014                 steps = view->height > view->lineno
2015                       ? -view->lineno : -view->height;
2016                 break;
2018         case REQ_MOVE_PAGE_DOWN:
2019                 steps = view->lineno + view->height >= view->lines
2020                       ? view->lines - view->lineno - 1 : view->height;
2021                 break;
2023         case REQ_MOVE_UP:
2024                 steps = -1;
2025                 break;
2027         case REQ_MOVE_DOWN:
2028                 steps = 1;
2029                 break;
2031         default:
2032                 die("request %d not handled in switch", request);
2033         }
2035         if (steps <= 0 && view->lineno == 0) {
2036                 report("Cannot move beyond the first line");
2037                 return;
2039         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2040                 report("Cannot move beyond the last line");
2041                 return;
2042         }
2044         /* Move the current line */
2045         view->lineno += steps;
2046         assert(0 <= view->lineno && view->lineno < view->lines);
2048         /* Check whether the view needs to be scrolled */
2049         if (view->lineno < view->offset ||
2050             view->lineno >= view->offset + view->height) {
2051                 scroll_steps = steps;
2052                 if (steps < 0 && -steps > view->offset) {
2053                         scroll_steps = -view->offset;
2055                 } else if (steps > 0) {
2056                         if (view->lineno == view->lines - 1 &&
2057                             view->lines > view->height) {
2058                                 scroll_steps = view->lines - view->offset - 1;
2059                                 if (scroll_steps >= view->height)
2060                                         scroll_steps -= view->height - 1;
2061                         }
2062                 }
2063         }
2065         if (!view_is_displayed(view)) {
2066                 view->offset += scroll_steps;
2067                 assert(0 <= view->offset && view->offset < view->lines);
2068                 view->ops->select(view, &view->line[view->lineno]);
2069                 return;
2070         }
2072         /* Repaint the old "current" line if we be scrolling */
2073         if (ABS(steps) < view->height)
2074                 draw_view_line(view, view->lineno - steps - view->offset);
2076         if (scroll_steps) {
2077                 do_scroll_view(view, scroll_steps);
2078                 return;
2079         }
2081         /* Draw the current line */
2082         draw_view_line(view, view->lineno - view->offset);
2084         redrawwin(view->win);
2085         wrefresh(view->win);
2086         report("");
2090 /*
2091  * Searching
2092  */
2094 static void search_view(struct view *view, enum request request);
2096 static bool
2097 find_next_line(struct view *view, unsigned long lineno, struct line *line)
2099         assert(view_is_displayed(view));
2101         if (!view->ops->grep(view, line))
2102                 return FALSE;
2104         if (lineno - view->offset >= view->height) {
2105                 view->offset = lineno;
2106                 view->lineno = lineno;
2107                 redraw_view(view);
2109         } else {
2110                 unsigned long old_lineno = view->lineno - view->offset;
2112                 view->lineno = lineno;
2113                 draw_view_line(view, old_lineno);
2115                 draw_view_line(view, view->lineno - view->offset);
2116                 redrawwin(view->win);
2117                 wrefresh(view->win);
2118         }
2120         report("Line %ld matches '%s'", lineno + 1, view->grep);
2121         return TRUE;
2124 static void
2125 find_next(struct view *view, enum request request)
2127         unsigned long lineno = view->lineno;
2128         int direction;
2130         if (!*view->grep) {
2131                 if (!*opt_search)
2132                         report("No previous search");
2133                 else
2134                         search_view(view, request);
2135                 return;
2136         }
2138         switch (request) {
2139         case REQ_SEARCH:
2140         case REQ_FIND_NEXT:
2141                 direction = 1;
2142                 break;
2144         case REQ_SEARCH_BACK:
2145         case REQ_FIND_PREV:
2146                 direction = -1;
2147                 break;
2149         default:
2150                 return;
2151         }
2153         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2154                 lineno += direction;
2156         /* Note, lineno is unsigned long so will wrap around in which case it
2157          * will become bigger than view->lines. */
2158         for (; lineno < view->lines; lineno += direction) {
2159                 struct line *line = &view->line[lineno];
2161                 if (find_next_line(view, lineno, line))
2162                         return;
2163         }
2165         report("No match found for '%s'", view->grep);
2168 static void
2169 search_view(struct view *view, enum request request)
2171         int regex_err;
2173         if (view->regex) {
2174                 regfree(view->regex);
2175                 *view->grep = 0;
2176         } else {
2177                 view->regex = calloc(1, sizeof(*view->regex));
2178                 if (!view->regex)
2179                         return;
2180         }
2182         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2183         if (regex_err != 0) {
2184                 char buf[SIZEOF_STR] = "unknown error";
2186                 regerror(regex_err, view->regex, buf, sizeof(buf));
2187                 report("Search failed: %s", buf);
2188                 return;
2189         }
2191         string_copy(view->grep, opt_search);
2193         find_next(view, request);
2196 /*
2197  * Incremental updating
2198  */
2200 static void
2201 reset_view(struct view *view)
2203         int i;
2205         for (i = 0; i < view->lines; i++)
2206                 free(view->line[i].data);
2207         free(view->line);
2209         view->line = NULL;
2210         view->offset = 0;
2211         view->lines  = 0;
2212         view->lineno = 0;
2213         view->line_size = 0;
2214         view->line_alloc = 0;
2215         view->vid[0] = 0;
2218 static void
2219 end_update(struct view *view, bool force)
2221         if (!view->pipe)
2222                 return;
2223         while (!view->ops->read(view, NULL))
2224                 if (!force)
2225                         return;
2226         set_nonblocking_input(FALSE);
2227         if (view->pipe == stdin)
2228                 fclose(view->pipe);
2229         else
2230                 pclose(view->pipe);
2231         view->pipe = NULL;
2234 static bool
2235 begin_update(struct view *view, bool refresh)
2237         if (opt_cmd[0]) {
2238                 string_copy(view->cmd, opt_cmd);
2239                 opt_cmd[0] = 0;
2240                 /* When running random commands, initially show the
2241                  * command in the title. However, it maybe later be
2242                  * overwritten if a commit line is selected. */
2243                 if (view == VIEW(REQ_VIEW_PAGER))
2244                         string_copy(view->ref, view->cmd);
2245                 else
2246                         view->ref[0] = 0;
2248         } else if (view == VIEW(REQ_VIEW_TREE)) {
2249                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
2250                 char path[SIZEOF_STR];
2252                 if (strcmp(view->vid, view->id))
2253                         opt_path[0] = path[0] = 0;
2254                 else if (sq_quote(path, 0, opt_path) >= sizeof(path))
2255                         return FALSE;
2257                 if (!string_format(view->cmd, format, view->id, path))
2258                         return FALSE;
2260         } else if (!refresh) {
2261                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
2262                 const char *id = view->id;
2264                 if (!string_format(view->cmd, format, id, id, id, id, id))
2265                         return FALSE;
2267                 /* Put the current ref_* value to the view title ref
2268                  * member. This is needed by the blob view. Most other
2269                  * views sets it automatically after loading because the
2270                  * first line is a commit line. */
2271                 string_copy_rev(view->ref, view->id);
2272         }
2274         /* Special case for the pager view. */
2275         if (opt_pipe) {
2276                 view->pipe = opt_pipe;
2277                 opt_pipe = NULL;
2278         } else {
2279                 view->pipe = popen(view->cmd, "r");
2280         }
2282         if (!view->pipe)
2283                 return FALSE;
2285         set_nonblocking_input(TRUE);
2286         reset_view(view);
2287         string_copy_rev(view->vid, view->id);
2289         view->start_time = time(NULL);
2291         return TRUE;
2294 #define ITEM_CHUNK_SIZE 256
2295 static void *
2296 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2298         size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2299         size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2301         if (mem == NULL || num_chunks != num_chunks_new) {
2302                 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2303                 mem = realloc(mem, *size * item_size);
2304         }
2306         return mem;
2309 static struct line *
2310 realloc_lines(struct view *view, size_t line_size)
2312         size_t alloc = view->line_alloc;
2313         struct line *tmp = realloc_items(view->line, &alloc, line_size,
2314                                          sizeof(*view->line));
2316         if (!tmp)
2317                 return NULL;
2319         view->line = tmp;
2320         view->line_alloc = alloc;
2321         view->line_size = line_size;
2322         return view->line;
2325 static bool
2326 update_view(struct view *view)
2328         char in_buffer[BUFSIZ];
2329         char out_buffer[BUFSIZ * 2];
2330         char *line;
2331         /* The number of lines to read. If too low it will cause too much
2332          * redrawing (and possible flickering), if too high responsiveness
2333          * will suffer. */
2334         unsigned long lines = view->height;
2335         int redraw_from = -1;
2337         if (!view->pipe)
2338                 return TRUE;
2340         /* Only redraw if lines are visible. */
2341         if (view->offset + view->height >= view->lines)
2342                 redraw_from = view->lines - view->offset;
2344         /* FIXME: This is probably not perfect for backgrounded views. */
2345         if (!realloc_lines(view, view->lines + lines))
2346                 goto alloc_error;
2348         while ((line = fgets(in_buffer, sizeof(in_buffer), view->pipe))) {
2349                 size_t linelen = strlen(line);
2351                 if (linelen)
2352                         line[linelen - 1] = 0;
2354                 if (opt_iconv != ICONV_NONE) {
2355                         ICONV_CONST char *inbuf = line;
2356                         size_t inlen = linelen;
2358                         char *outbuf = out_buffer;
2359                         size_t outlen = sizeof(out_buffer);
2361                         size_t ret;
2363                         ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2364                         if (ret != (size_t) -1) {
2365                                 line = out_buffer;
2366                                 linelen = strlen(out_buffer);
2367                         }
2368                 }
2370                 if (!view->ops->read(view, line))
2371                         goto alloc_error;
2373                 if (lines-- == 1)
2374                         break;
2375         }
2377         {
2378                 int digits;
2380                 lines = view->lines;
2381                 for (digits = 0; lines; digits++)
2382                         lines /= 10;
2384                 /* Keep the displayed view in sync with line number scaling. */
2385                 if (digits != view->digits) {
2386                         view->digits = digits;
2387                         redraw_from = 0;
2388                 }
2389         }
2391         if (ferror(view->pipe) && errno != 0) {
2392                 report("Failed to read: %s", strerror(errno));
2393                 end_update(view, TRUE);
2395         } else if (feof(view->pipe)) {
2396                 report("");
2397                 end_update(view, FALSE);
2398         }
2400         if (view == VIEW(REQ_VIEW_TREE)) {
2401                 /* Clear the view and redraw everything since the tree sorting
2402                  * might have rearranged things. */
2403                 redraw_view(view);
2405         } else if (redraw_from >= 0) {
2406                 /* If this is an incremental update, redraw the previous line
2407                  * since for commits some members could have changed when
2408                  * loading the main view. */
2409                 if (redraw_from > 0)
2410                         redraw_from--;
2412                 /* Since revision graph visualization requires knowledge
2413                  * about the parent commit, it causes a further one-off
2414                  * needed to be redrawn for incremental updates. */
2415                 if (redraw_from > 0 && opt_rev_graph)
2416                         redraw_from--;
2418                 /* Incrementally draw avoids flickering. */
2419                 redraw_view_from(view, redraw_from);
2420         }
2422         if (view == VIEW(REQ_VIEW_BLAME))
2423                 redraw_view_dirty(view);
2425         /* Update the title _after_ the redraw so that if the redraw picks up a
2426          * commit reference in view->ref it'll be available here. */
2427         update_view_title(view);
2428         return TRUE;
2430 alloc_error:
2431         report("Allocation failure");
2432         end_update(view, TRUE);
2433         return FALSE;
2436 static struct line *
2437 add_line_data(struct view *view, void *data, enum line_type type)
2439         struct line *line = &view->line[view->lines++];
2441         memset(line, 0, sizeof(*line));
2442         line->type = type;
2443         line->data = data;
2445         return line;
2448 static struct line *
2449 add_line_text(struct view *view, const char *text, enum line_type type)
2451         char *data = text ? strdup(text) : NULL;
2453         return data ? add_line_data(view, data, type) : NULL;
2457 /*
2458  * View opening
2459  */
2461 enum open_flags {
2462         OPEN_DEFAULT = 0,       /* Use default view switching. */
2463         OPEN_SPLIT = 1,         /* Split current view. */
2464         OPEN_BACKGROUNDED = 2,  /* Backgrounded. */
2465         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
2466         OPEN_NOMAXIMIZE = 8,    /* Do not maximize the current view. */
2467         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
2468 };
2470 static void
2471 open_view(struct view *prev, enum request request, enum open_flags flags)
2473         bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2474         bool split = !!(flags & OPEN_SPLIT);
2475         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH));
2476         bool nomaximize = !!(flags & (OPEN_NOMAXIMIZE | OPEN_REFRESH));
2477         struct view *view = VIEW(request);
2478         int nviews = displayed_views();
2479         struct view *base_view = display[0];
2481         if (view == prev && nviews == 1 && !reload) {
2482                 report("Already in %s view", view->name);
2483                 return;
2484         }
2486         if (view->git_dir && !opt_git_dir[0]) {
2487                 report("The %s view is disabled in pager view", view->name);
2488                 return;
2489         }
2491         if (split) {
2492                 display[1] = view;
2493                 if (!backgrounded)
2494                         current_view = 1;
2495         } else if (!nomaximize) {
2496                 /* Maximize the current view. */
2497                 memset(display, 0, sizeof(display));
2498                 current_view = 0;
2499                 display[current_view] = view;
2500         }
2502         /* Resize the view when switching between split- and full-screen,
2503          * or when switching between two different full-screen views. */
2504         if (nviews != displayed_views() ||
2505             (nviews == 1 && base_view != display[0]))
2506                 resize_display();
2508         if (view->pipe)
2509                 end_update(view, TRUE);
2511         if (view->ops->open) {
2512                 if (!view->ops->open(view)) {
2513                         report("Failed to load %s view", view->name);
2514                         return;
2515                 }
2517         } else if ((reload || strcmp(view->vid, view->id)) &&
2518                    !begin_update(view, flags & OPEN_REFRESH)) {
2519                 report("Failed to load %s view", view->name);
2520                 return;
2521         }
2523         if (split && prev->lineno - prev->offset >= prev->height) {
2524                 /* Take the title line into account. */
2525                 int lines = prev->lineno - prev->offset - prev->height + 1;
2527                 /* Scroll the view that was split if the current line is
2528                  * outside the new limited view. */
2529                 do_scroll_view(prev, lines);
2530         }
2532         if (prev && view != prev) {
2533                 if (split && !backgrounded) {
2534                         /* "Blur" the previous view. */
2535                         update_view_title(prev);
2536                 }
2538                 view->parent = prev;
2539         }
2541         if (view->pipe && view->lines == 0) {
2542                 /* Clear the old view and let the incremental updating refill
2543                  * the screen. */
2544                 werase(view->win);
2545                 report("");
2546         } else if (view_is_displayed(view)) {
2547                 redraw_view(view);
2548                 report("");
2549         }
2551         /* If the view is backgrounded the above calls to report()
2552          * won't redraw the view title. */
2553         if (backgrounded)
2554                 update_view_title(view);
2557 static bool
2558 run_confirm(const char *cmd, const char *prompt)
2560         bool confirmation = prompt_yesno(prompt);
2562         if (confirmation)
2563                 system(cmd);
2565         return confirmation;
2568 static void
2569 open_external_viewer(const char *cmd)
2571         def_prog_mode();           /* save current tty modes */
2572         endwin();                  /* restore original tty modes */
2573         system(cmd);
2574         fprintf(stderr, "Press Enter to continue");
2575         getc(opt_tty);
2576         reset_prog_mode();
2577         redraw_display();
2580 static void
2581 open_mergetool(const char *file)
2583         char cmd[SIZEOF_STR];
2584         char file_sq[SIZEOF_STR];
2586         if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2587             string_format(cmd, "git mergetool %s", file_sq)) {
2588                 open_external_viewer(cmd);
2589         }
2592 static void
2593 open_editor(bool from_root, const char *file)
2595         char cmd[SIZEOF_STR];
2596         char file_sq[SIZEOF_STR];
2597         const char *editor;
2598         char *prefix = from_root ? opt_cdup : "";
2600         editor = getenv("GIT_EDITOR");
2601         if (!editor && *opt_editor)
2602                 editor = opt_editor;
2603         if (!editor)
2604                 editor = getenv("VISUAL");
2605         if (!editor)
2606                 editor = getenv("EDITOR");
2607         if (!editor)
2608                 editor = "vi";
2610         if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2611             string_format(cmd, "%s %s%s", editor, prefix, file_sq)) {
2612                 open_external_viewer(cmd);
2613         }
2616 static void
2617 open_run_request(enum request request)
2619         struct run_request *req = get_run_request(request);
2620         char buf[SIZEOF_STR * 2];
2621         size_t bufpos;
2622         char *cmd;
2624         if (!req) {
2625                 report("Unknown run request");
2626                 return;
2627         }
2629         bufpos = 0;
2630         cmd = req->cmd;
2632         while (cmd) {
2633                 char *next = strstr(cmd, "%(");
2634                 int len = next - cmd;
2635                 char *value;
2637                 if (!next) {
2638                         len = strlen(cmd);
2639                         value = "";
2641                 } else if (!strncmp(next, "%(head)", 7)) {
2642                         value = ref_head;
2644                 } else if (!strncmp(next, "%(commit)", 9)) {
2645                         value = ref_commit;
2647                 } else if (!strncmp(next, "%(blob)", 7)) {
2648                         value = ref_blob;
2650                 } else {
2651                         report("Unknown replacement in run request: `%s`", req->cmd);
2652                         return;
2653                 }
2655                 if (!string_format_from(buf, &bufpos, "%.*s%s", len, cmd, value))
2656                         return;
2658                 if (next)
2659                         next = strchr(next, ')') + 1;
2660                 cmd = next;
2661         }
2663         open_external_viewer(buf);
2666 /*
2667  * User request switch noodle
2668  */
2670 static int
2671 view_driver(struct view *view, enum request request)
2673         int i;
2675         if (request == REQ_NONE) {
2676                 doupdate();
2677                 return TRUE;
2678         }
2680         if (request > REQ_NONE) {
2681                 open_run_request(request);
2682                 /* FIXME: When all views can refresh always do this. */
2683                 if (view == VIEW(REQ_VIEW_STATUS) ||
2684                     view == VIEW(REQ_VIEW_MAIN) ||
2685                     view == VIEW(REQ_VIEW_LOG) ||
2686                     view == VIEW(REQ_VIEW_STAGE))
2687                         request = REQ_REFRESH;
2688                 else
2689                         return TRUE;
2690         }
2692         if (view && view->lines) {
2693                 request = view->ops->request(view, request, &view->line[view->lineno]);
2694                 if (request == REQ_NONE)
2695                         return TRUE;
2696         }
2698         switch (request) {
2699         case REQ_MOVE_UP:
2700         case REQ_MOVE_DOWN:
2701         case REQ_MOVE_PAGE_UP:
2702         case REQ_MOVE_PAGE_DOWN:
2703         case REQ_MOVE_FIRST_LINE:
2704         case REQ_MOVE_LAST_LINE:
2705                 move_view(view, request);
2706                 break;
2708         case REQ_SCROLL_LINE_DOWN:
2709         case REQ_SCROLL_LINE_UP:
2710         case REQ_SCROLL_PAGE_DOWN:
2711         case REQ_SCROLL_PAGE_UP:
2712                 scroll_view(view, request);
2713                 break;
2715         case REQ_VIEW_BLAME:
2716                 if (!opt_file[0]) {
2717                         report("No file chosen, press %s to open tree view",
2718                                get_key(REQ_VIEW_TREE));
2719                         break;
2720                 }
2721                 open_view(view, request, OPEN_DEFAULT);
2722                 break;
2724         case REQ_VIEW_BLOB:
2725                 if (!ref_blob[0]) {
2726                         report("No file chosen, press %s to open tree view",
2727                                get_key(REQ_VIEW_TREE));
2728                         break;
2729                 }
2730                 open_view(view, request, OPEN_DEFAULT);
2731                 break;
2733         case REQ_VIEW_PAGER:
2734                 if (!opt_pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
2735                         report("No pager content, press %s to run command from prompt",
2736                                get_key(REQ_PROMPT));
2737                         break;
2738                 }
2739                 open_view(view, request, OPEN_DEFAULT);
2740                 break;
2742         case REQ_VIEW_STAGE:
2743                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
2744                         report("No stage content, press %s to open the status view and choose file",
2745                                get_key(REQ_VIEW_STATUS));
2746                         break;
2747                 }
2748                 open_view(view, request, OPEN_DEFAULT);
2749                 break;
2751         case REQ_VIEW_STATUS:
2752                 if (opt_is_inside_work_tree == FALSE) {
2753                         report("The status view requires a working tree");
2754                         break;
2755                 }
2756                 open_view(view, request, OPEN_DEFAULT);
2757                 break;
2759         case REQ_VIEW_MAIN:
2760         case REQ_VIEW_DIFF:
2761         case REQ_VIEW_LOG:
2762         case REQ_VIEW_TREE:
2763         case REQ_VIEW_HELP:
2764                 open_view(view, request, OPEN_DEFAULT);
2765                 break;
2767         case REQ_NEXT:
2768         case REQ_PREVIOUS:
2769                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
2771                 if ((view == VIEW(REQ_VIEW_DIFF) &&
2772                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
2773                    (view == VIEW(REQ_VIEW_DIFF) &&
2774                      view->parent == VIEW(REQ_VIEW_BLAME)) ||
2775                    (view == VIEW(REQ_VIEW_STAGE) &&
2776                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
2777                    (view == VIEW(REQ_VIEW_BLOB) &&
2778                      view->parent == VIEW(REQ_VIEW_TREE))) {
2779                         int line;
2781                         view = view->parent;
2782                         line = view->lineno;
2783                         move_view(view, request);
2784                         if (view_is_displayed(view))
2785                                 update_view_title(view);
2786                         if (line != view->lineno)
2787                                 view->ops->request(view, REQ_ENTER,
2788                                                    &view->line[view->lineno]);
2790                 } else {
2791                         move_view(view, request);
2792                 }
2793                 break;
2795         case REQ_VIEW_NEXT:
2796         {
2797                 int nviews = displayed_views();
2798                 int next_view = (current_view + 1) % nviews;
2800                 if (next_view == current_view) {
2801                         report("Only one view is displayed");
2802                         break;
2803                 }
2805                 current_view = next_view;
2806                 /* Blur out the title of the previous view. */
2807                 update_view_title(view);
2808                 report("");
2809                 break;
2810         }
2811         case REQ_REFRESH:
2812                 report("Refreshing is not yet supported for the %s view", view->name);
2813                 break;
2815         case REQ_MAXIMIZE:
2816                 if (displayed_views() == 2)
2817                         open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
2818                 break;
2820         case REQ_TOGGLE_LINENO:
2821                 opt_line_number = !opt_line_number;
2822                 redraw_display();
2823                 break;
2825         case REQ_TOGGLE_DATE:
2826                 opt_date = !opt_date;
2827                 redraw_display();
2828                 break;
2830         case REQ_TOGGLE_AUTHOR:
2831                 opt_author = !opt_author;
2832                 redraw_display();
2833                 break;
2835         case REQ_TOGGLE_REV_GRAPH:
2836                 opt_rev_graph = !opt_rev_graph;
2837                 redraw_display();
2838                 break;
2840         case REQ_TOGGLE_REFS:
2841                 opt_show_refs = !opt_show_refs;
2842                 redraw_display();
2843                 break;
2845         case REQ_SEARCH:
2846         case REQ_SEARCH_BACK:
2847                 search_view(view, request);
2848                 break;
2850         case REQ_FIND_NEXT:
2851         case REQ_FIND_PREV:
2852                 find_next(view, request);
2853                 break;
2855         case REQ_STOP_LOADING:
2856                 for (i = 0; i < ARRAY_SIZE(views); i++) {
2857                         view = &views[i];
2858                         if (view->pipe)
2859                                 report("Stopped loading the %s view", view->name),
2860                         end_update(view, TRUE);
2861                 }
2862                 break;
2864         case REQ_SHOW_VERSION:
2865                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
2866                 return TRUE;
2868         case REQ_SCREEN_RESIZE:
2869                 resize_display();
2870                 /* Fall-through */
2871         case REQ_SCREEN_REDRAW:
2872                 redraw_display();
2873                 break;
2875         case REQ_EDIT:
2876                 report("Nothing to edit");
2877                 break;
2879         case REQ_ENTER:
2880                 report("Nothing to enter");
2881                 break;
2883         case REQ_VIEW_CLOSE:
2884                 /* XXX: Mark closed views by letting view->parent point to the
2885                  * view itself. Parents to closed view should never be
2886                  * followed. */
2887                 if (view->parent &&
2888                     view->parent->parent != view->parent) {
2889                         memset(display, 0, sizeof(display));
2890                         current_view = 0;
2891                         display[current_view] = view->parent;
2892                         view->parent = view;
2893                         resize_display();
2894                         redraw_display();
2895                         report("");
2896                         break;
2897                 }
2898                 /* Fall-through */
2899         case REQ_QUIT:
2900                 return FALSE;
2902         default:
2903                 report("Unknown key, press 'h' for help");
2904                 return TRUE;
2905         }
2907         return TRUE;
2911 /*
2912  * Pager backend
2913  */
2915 static bool
2916 pager_draw(struct view *view, struct line *line, unsigned int lineno)
2918         char *text = line->data;
2920         if (opt_line_number && draw_lineno(view, lineno))
2921                 return TRUE;
2923         draw_text(view, line->type, text, TRUE);
2924         return TRUE;
2927 static bool
2928 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
2930         char refbuf[SIZEOF_STR];
2931         char *ref = NULL;
2932         FILE *pipe;
2934         if (!string_format(refbuf, "git describe %s 2>/dev/null", commit_id))
2935                 return TRUE;
2937         pipe = popen(refbuf, "r");
2938         if (!pipe)
2939                 return TRUE;
2941         if ((ref = fgets(refbuf, sizeof(refbuf), pipe)))
2942                 ref = chomp_string(ref);
2943         pclose(pipe);
2945         if (!ref || !*ref)
2946                 return TRUE;
2948         /* This is the only fatal call, since it can "corrupt" the buffer. */
2949         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
2950                 return FALSE;
2952         return TRUE;
2955 static void
2956 add_pager_refs(struct view *view, struct line *line)
2958         char buf[SIZEOF_STR];
2959         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
2960         struct ref **refs;
2961         size_t bufpos = 0, refpos = 0;
2962         const char *sep = "Refs: ";
2963         bool is_tag = FALSE;
2965         assert(line->type == LINE_COMMIT);
2967         refs = get_refs(commit_id);
2968         if (!refs) {
2969                 if (view == VIEW(REQ_VIEW_DIFF))
2970                         goto try_add_describe_ref;
2971                 return;
2972         }
2974         do {
2975                 struct ref *ref = refs[refpos];
2976                 const char *fmt = ref->tag    ? "%s[%s]" :
2977                                   ref->remote ? "%s<%s>" : "%s%s";
2979                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
2980                         return;
2981                 sep = ", ";
2982                 if (ref->tag)
2983                         is_tag = TRUE;
2984         } while (refs[refpos++]->next);
2986         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
2987 try_add_describe_ref:
2988                 /* Add <tag>-g<commit_id> "fake" reference. */
2989                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
2990                         return;
2991         }
2993         if (bufpos == 0)
2994                 return;
2996         if (!realloc_lines(view, view->line_size + 1))
2997                 return;
2999         add_line_text(view, buf, LINE_PP_REFS);
3002 static bool
3003 pager_read(struct view *view, char *data)
3005         struct line *line;
3007         if (!data)
3008                 return TRUE;
3010         line = add_line_text(view, data, get_line_type(data));
3011         if (!line)
3012                 return FALSE;
3014         if (line->type == LINE_COMMIT &&
3015             (view == VIEW(REQ_VIEW_DIFF) ||
3016              view == VIEW(REQ_VIEW_LOG)))
3017                 add_pager_refs(view, line);
3019         return TRUE;
3022 static enum request
3023 pager_request(struct view *view, enum request request, struct line *line)
3025         int split = 0;
3027         if (request != REQ_ENTER)
3028                 return request;
3030         if (line->type == LINE_COMMIT &&
3031            (view == VIEW(REQ_VIEW_LOG) ||
3032             view == VIEW(REQ_VIEW_PAGER))) {
3033                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3034                 split = 1;
3035         }
3037         /* Always scroll the view even if it was split. That way
3038          * you can use Enter to scroll through the log view and
3039          * split open each commit diff. */
3040         scroll_view(view, REQ_SCROLL_LINE_DOWN);
3042         /* FIXME: A minor workaround. Scrolling the view will call report("")
3043          * but if we are scrolling a non-current view this won't properly
3044          * update the view title. */
3045         if (split)
3046                 update_view_title(view);
3048         return REQ_NONE;
3051 static bool
3052 pager_grep(struct view *view, struct line *line)
3054         regmatch_t pmatch;
3055         char *text = line->data;
3057         if (!*text)
3058                 return FALSE;
3060         if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3061                 return FALSE;
3063         return TRUE;
3066 static void
3067 pager_select(struct view *view, struct line *line)
3069         if (line->type == LINE_COMMIT) {
3070                 char *text = (char *)line->data + STRING_SIZE("commit ");
3072                 if (view != VIEW(REQ_VIEW_PAGER))
3073                         string_copy_rev(view->ref, text);
3074                 string_copy_rev(ref_commit, text);
3075         }
3078 static struct view_ops pager_ops = {
3079         "line",
3080         NULL,
3081         pager_read,
3082         pager_draw,
3083         pager_request,
3084         pager_grep,
3085         pager_select,
3086 };
3088 static enum request
3089 log_request(struct view *view, enum request request, struct line *line)
3091         switch (request) {
3092         case REQ_REFRESH:
3093                 load_refs();
3094                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3095                 return REQ_NONE;
3096         default:
3097                 return pager_request(view, request, line);
3098         }
3101 static struct view_ops log_ops = {
3102         "line",
3103         NULL,
3104         pager_read,
3105         pager_draw,
3106         log_request,
3107         pager_grep,
3108         pager_select,
3109 };
3112 /*
3113  * Help backend
3114  */
3116 static bool
3117 help_open(struct view *view)
3119         char buf[BUFSIZ];
3120         int lines = ARRAY_SIZE(req_info) + 2;
3121         int i;
3123         if (view->lines > 0)
3124                 return TRUE;
3126         for (i = 0; i < ARRAY_SIZE(req_info); i++)
3127                 if (!req_info[i].request)
3128                         lines++;
3130         lines += run_requests + 1;
3132         view->line = calloc(lines, sizeof(*view->line));
3133         if (!view->line)
3134                 return FALSE;
3136         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3138         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3139                 const char *key;
3141                 if (req_info[i].request == REQ_NONE)
3142                         continue;
3144                 if (!req_info[i].request) {
3145                         add_line_text(view, "", LINE_DEFAULT);
3146                         add_line_text(view, req_info[i].help, LINE_DEFAULT);
3147                         continue;
3148                 }
3150                 key = get_key(req_info[i].request);
3151                 if (!*key)
3152                         key = "(no key defined)";
3154                 if (!string_format(buf, "    %-25s %s", key, req_info[i].help))
3155                         continue;
3157                 add_line_text(view, buf, LINE_DEFAULT);
3158         }
3160         if (run_requests) {
3161                 add_line_text(view, "", LINE_DEFAULT);
3162                 add_line_text(view, "External commands:", LINE_DEFAULT);
3163         }
3165         for (i = 0; i < run_requests; i++) {
3166                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3167                 const char *key;
3169                 if (!req)
3170                         continue;
3172                 key = get_key_name(req->key);
3173                 if (!*key)
3174                         key = "(no key defined)";
3176                 if (!string_format(buf, "    %-10s %-14s `%s`",
3177                                    keymap_table[req->keymap].name,
3178                                    key, req->cmd))
3179                         continue;
3181                 add_line_text(view, buf, LINE_DEFAULT);
3182         }
3184         return TRUE;
3187 static struct view_ops help_ops = {
3188         "line",
3189         help_open,
3190         NULL,
3191         pager_draw,
3192         pager_request,
3193         pager_grep,
3194         pager_select,
3195 };
3198 /*
3199  * Tree backend
3200  */
3202 struct tree_stack_entry {
3203         struct tree_stack_entry *prev;  /* Entry below this in the stack */
3204         unsigned long lineno;           /* Line number to restore */
3205         char *name;                     /* Position of name in opt_path */
3206 };
3208 /* The top of the path stack. */
3209 static struct tree_stack_entry *tree_stack = NULL;
3210 unsigned long tree_lineno = 0;
3212 static void
3213 pop_tree_stack_entry(void)
3215         struct tree_stack_entry *entry = tree_stack;
3217         tree_lineno = entry->lineno;
3218         entry->name[0] = 0;
3219         tree_stack = entry->prev;
3220         free(entry);
3223 static void
3224 push_tree_stack_entry(const char *name, unsigned long lineno)
3226         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3227         size_t pathlen = strlen(opt_path);
3229         if (!entry)
3230                 return;
3232         entry->prev = tree_stack;
3233         entry->name = opt_path + pathlen;
3234         tree_stack = entry;
3236         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3237                 pop_tree_stack_entry();
3238                 return;
3239         }
3241         /* Move the current line to the first tree entry. */
3242         tree_lineno = 1;
3243         entry->lineno = lineno;
3246 /* Parse output from git-ls-tree(1):
3247  *
3248  * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3249  * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3250  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3251  * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3252  */
3254 #define SIZEOF_TREE_ATTR \
3255         STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3257 #define TREE_UP_FORMAT "040000 tree %s\t.."
3259 static int
3260 tree_compare_entry(enum line_type type1, const char *name1,
3261                    enum line_type type2, const char *name2)
3263         if (type1 != type2) {
3264                 if (type1 == LINE_TREE_DIR)
3265                         return -1;
3266                 return 1;
3267         }
3269         return strcmp(name1, name2);
3272 static const char *
3273 tree_path(struct line *line)
3275         const char *path = line->data;
3277         return path + SIZEOF_TREE_ATTR;
3280 static bool
3281 tree_read(struct view *view, char *text)
3283         size_t textlen = text ? strlen(text) : 0;
3284         char buf[SIZEOF_STR];
3285         unsigned long pos;
3286         enum line_type type;
3287         bool first_read = view->lines == 0;
3289         if (!text)
3290                 return TRUE;
3291         if (textlen <= SIZEOF_TREE_ATTR)
3292                 return FALSE;
3294         type = text[STRING_SIZE("100644 ")] == 't'
3295              ? LINE_TREE_DIR : LINE_TREE_FILE;
3297         if (first_read) {
3298                 /* Add path info line */
3299                 if (!string_format(buf, "Directory path /%s", opt_path) ||
3300                     !realloc_lines(view, view->line_size + 1) ||
3301                     !add_line_text(view, buf, LINE_DEFAULT))
3302                         return FALSE;
3304                 /* Insert "link" to parent directory. */
3305                 if (*opt_path) {
3306                         if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
3307                             !realloc_lines(view, view->line_size + 1) ||
3308                             !add_line_text(view, buf, LINE_TREE_DIR))
3309                                 return FALSE;
3310                 }
3311         }
3313         /* Strip the path part ... */
3314         if (*opt_path) {
3315                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3316                 size_t striplen = strlen(opt_path);
3317                 char *path = text + SIZEOF_TREE_ATTR;
3319                 if (pathlen > striplen)
3320                         memmove(path, path + striplen,
3321                                 pathlen - striplen + 1);
3322         }
3324         /* Skip "Directory ..." and ".." line. */
3325         for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
3326                 struct line *line = &view->line[pos];
3327                 const char *path1 = tree_path(line);
3328                 char *path2 = text + SIZEOF_TREE_ATTR;
3329                 int cmp = tree_compare_entry(line->type, path1, type, path2);
3331                 if (cmp <= 0)
3332                         continue;
3334                 text = strdup(text);
3335                 if (!text)
3336                         return FALSE;
3338                 if (view->lines > pos)
3339                         memmove(&view->line[pos + 1], &view->line[pos],
3340                                 (view->lines - pos) * sizeof(*line));
3342                 line = &view->line[pos];
3343                 line->data = text;
3344                 line->type = type;
3345                 view->lines++;
3346                 return TRUE;
3347         }
3349         if (!add_line_text(view, text, type))
3350                 return FALSE;
3352         if (tree_lineno > view->lineno) {
3353                 view->lineno = tree_lineno;
3354                 tree_lineno = 0;
3355         }
3357         return TRUE;
3360 static enum request
3361 tree_request(struct view *view, enum request request, struct line *line)
3363         enum open_flags flags;
3365         switch (request) {
3366         case REQ_VIEW_BLAME:
3367                 if (line->type != LINE_TREE_FILE) {
3368                         report("Blame only supported for files");
3369                         return REQ_NONE;
3370                 }
3372                 string_copy(opt_ref, view->vid);
3373                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
3374                 return request;
3376         case REQ_TREE_PARENT:
3377                 if (!*opt_path) {
3378                         /* quit view if at top of tree */
3379                         return REQ_VIEW_CLOSE;
3380                 }
3381                 /* fake 'cd  ..' */
3382                 line = &view->line[1];
3383                 break;
3385         case REQ_ENTER:
3386                 break;
3388         default:
3389                 return request;
3390         }
3392         /* Cleanup the stack if the tree view is at a different tree. */
3393         while (!*opt_path && tree_stack)
3394                 pop_tree_stack_entry();
3396         switch (line->type) {
3397         case LINE_TREE_DIR:
3398                 /* Depending on whether it is a subdir or parent (updir?) link
3399                  * mangle the path buffer. */
3400                 if (line == &view->line[1] && *opt_path) {
3401                         pop_tree_stack_entry();
3403                 } else {
3404                         const char *basename = tree_path(line);
3406                         push_tree_stack_entry(basename, view->lineno);
3407                 }
3409                 /* Trees and subtrees share the same ID, so they are not not
3410                  * unique like blobs. */
3411                 flags = OPEN_RELOAD;
3412                 request = REQ_VIEW_TREE;
3413                 break;
3415         case LINE_TREE_FILE:
3416                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3417                 request = REQ_VIEW_BLOB;
3418                 break;
3420         default:
3421                 return TRUE;
3422         }
3424         open_view(view, request, flags);
3425         if (request == REQ_VIEW_TREE) {
3426                 view->lineno = tree_lineno;
3427         }
3429         return REQ_NONE;
3432 static void
3433 tree_select(struct view *view, struct line *line)
3435         char *text = (char *)line->data + STRING_SIZE("100644 blob ");
3437         if (line->type == LINE_TREE_FILE) {
3438                 string_copy_rev(ref_blob, text);
3440         } else if (line->type != LINE_TREE_DIR) {
3441                 return;
3442         }
3444         string_copy_rev(view->ref, text);
3447 static struct view_ops tree_ops = {
3448         "file",
3449         NULL,
3450         tree_read,
3451         pager_draw,
3452         tree_request,
3453         pager_grep,
3454         tree_select,
3455 };
3457 static bool
3458 blob_read(struct view *view, char *line)
3460         if (!line)
3461                 return TRUE;
3462         return add_line_text(view, line, LINE_DEFAULT) != NULL;
3465 static struct view_ops blob_ops = {
3466         "line",
3467         NULL,
3468         blob_read,
3469         pager_draw,
3470         pager_request,
3471         pager_grep,
3472         pager_select,
3473 };
3475 /*
3476  * Blame backend
3477  *
3478  * Loading the blame view is a two phase job:
3479  *
3480  *  1. File content is read either using opt_file from the
3481  *     filesystem or using git-cat-file.
3482  *  2. Then blame information is incrementally added by
3483  *     reading output from git-blame.
3484  */
3486 struct blame_commit {
3487         char id[SIZEOF_REV];            /* SHA1 ID. */
3488         char title[128];                /* First line of the commit message. */
3489         char author[75];                /* Author of the commit. */
3490         struct tm time;                 /* Date from the author ident. */
3491         char filename[128];             /* Name of file. */
3492 };
3494 struct blame {
3495         struct blame_commit *commit;
3496         unsigned int header:1;
3497         char text[1];
3498 };
3500 #define BLAME_CAT_FILE_CMD "git cat-file blob %s:%s"
3501 #define BLAME_INCREMENTAL_CMD "git blame --incremental %s -- %s"
3503 static bool
3504 blame_open(struct view *view)
3506         char path[SIZEOF_STR];
3507         char ref[SIZEOF_STR] = "";
3509         if (sq_quote(path, 0, opt_file) >= sizeof(path))
3510                 return FALSE;
3512         if (*opt_ref && sq_quote(ref, 0, opt_ref) >= sizeof(ref))
3513                 return FALSE;
3515         if (*opt_ref) {
3516                 if (!string_format(view->cmd, BLAME_CAT_FILE_CMD, ref, path))
3517                         return FALSE;
3518         } else {
3519                 view->pipe = fopen(opt_file, "r");
3520                 if (!view->pipe &&
3521                     !string_format(view->cmd, BLAME_CAT_FILE_CMD, "HEAD", path))
3522                         return FALSE;
3523         }
3525         if (!view->pipe)
3526                 view->pipe = popen(view->cmd, "r");
3527         if (!view->pipe)
3528                 return FALSE;
3530         if (!string_format(view->cmd, BLAME_INCREMENTAL_CMD, ref, path))
3531                 return FALSE;
3533         reset_view(view);
3534         string_format(view->ref, "%s ...", opt_file);
3535         string_copy_rev(view->vid, opt_file);
3536         set_nonblocking_input(TRUE);
3537         view->start_time = time(NULL);
3539         return TRUE;
3542 static struct blame_commit *
3543 get_blame_commit(struct view *view, const char *id)
3545         size_t i;
3547         for (i = 0; i < view->lines; i++) {
3548                 struct blame *blame = view->line[i].data;
3550                 if (!blame->commit)
3551                         continue;
3553                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
3554                         return blame->commit;
3555         }
3557         {
3558                 struct blame_commit *commit = calloc(1, sizeof(*commit));
3560                 if (commit)
3561                         string_ncopy(commit->id, id, SIZEOF_REV);
3562                 return commit;
3563         }
3566 static bool
3567 parse_number(const char **posref, size_t *number, size_t min, size_t max)
3569         const char *pos = *posref;
3571         *posref = NULL;
3572         pos = strchr(pos + 1, ' ');
3573         if (!pos || !isdigit(pos[1]))
3574                 return FALSE;
3575         *number = atoi(pos + 1);
3576         if (*number < min || *number > max)
3577                 return FALSE;
3579         *posref = pos;
3580         return TRUE;
3583 static struct blame_commit *
3584 parse_blame_commit(struct view *view, const char *text, int *blamed)
3586         struct blame_commit *commit;
3587         struct blame *blame;
3588         const char *pos = text + SIZEOF_REV - 1;
3589         size_t lineno;
3590         size_t group;
3592         if (strlen(text) <= SIZEOF_REV || *pos != ' ')
3593                 return NULL;
3595         if (!parse_number(&pos, &lineno, 1, view->lines) ||
3596             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
3597                 return NULL;
3599         commit = get_blame_commit(view, text);
3600         if (!commit)
3601                 return NULL;
3603         *blamed += group;
3604         while (group--) {
3605                 struct line *line = &view->line[lineno + group - 1];
3607                 blame = line->data;
3608                 blame->commit = commit;
3609                 blame->header = !group;
3610                 line->dirty = 1;
3611         }
3613         return commit;
3616 static bool
3617 blame_read_file(struct view *view, const char *line)
3619         if (!line) {
3620                 FILE *pipe = NULL;
3622                 if (view->lines > 0)
3623                         pipe = popen(view->cmd, "r");
3624                 else if (!view->parent)
3625                         die("No blame exist for %s", view->vid);
3626                 view->cmd[0] = 0;
3627                 if (!pipe) {
3628                         report("Failed to load blame data");
3629                         return TRUE;
3630                 }
3632                 fclose(view->pipe);
3633                 view->pipe = pipe;
3634                 return FALSE;
3636         } else {
3637                 size_t linelen = strlen(line);
3638                 struct blame *blame = malloc(sizeof(*blame) + linelen);
3640                 blame->commit = NULL;
3641                 strncpy(blame->text, line, linelen);
3642                 blame->text[linelen] = 0;
3643                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
3644         }
3647 static bool
3648 match_blame_header(const char *name, char **line)
3650         size_t namelen = strlen(name);
3651         bool matched = !strncmp(name, *line, namelen);
3653         if (matched)
3654                 *line += namelen;
3656         return matched;
3659 static bool
3660 blame_read(struct view *view, char *line)
3662         static struct blame_commit *commit = NULL;
3663         static int blamed = 0;
3664         static time_t author_time;
3666         if (*view->cmd)
3667                 return blame_read_file(view, line);
3669         if (!line) {
3670                 /* Reset all! */
3671                 commit = NULL;
3672                 blamed = 0;
3673                 string_format(view->ref, "%s", view->vid);
3674                 if (view_is_displayed(view)) {
3675                         update_view_title(view);
3676                         redraw_view_from(view, 0);
3677                 }
3678                 return TRUE;
3679         }
3681         if (!commit) {
3682                 commit = parse_blame_commit(view, line, &blamed);
3683                 string_format(view->ref, "%s %2d%%", view->vid,
3684                               blamed * 100 / view->lines);
3686         } else if (match_blame_header("author ", &line)) {
3687                 string_ncopy(commit->author, line, strlen(line));
3689         } else if (match_blame_header("author-time ", &line)) {
3690                 author_time = (time_t) atol(line);
3692         } else if (match_blame_header("author-tz ", &line)) {
3693                 long tz;
3695                 tz  = ('0' - line[1]) * 60 * 60 * 10;
3696                 tz += ('0' - line[2]) * 60 * 60;
3697                 tz += ('0' - line[3]) * 60;
3698                 tz += ('0' - line[4]) * 60;
3700                 if (line[0] == '-')
3701                         tz = -tz;
3703                 author_time -= tz;
3704                 gmtime_r(&author_time, &commit->time);
3706         } else if (match_blame_header("summary ", &line)) {
3707                 string_ncopy(commit->title, line, strlen(line));
3709         } else if (match_blame_header("filename ", &line)) {
3710                 string_ncopy(commit->filename, line, strlen(line));
3711                 commit = NULL;
3712         }
3714         return TRUE;
3717 static bool
3718 blame_draw(struct view *view, struct line *line, unsigned int lineno)
3720         struct blame *blame = line->data;
3721         struct tm *time = NULL;
3722         const char *id = NULL, *author = NULL;
3724         if (blame->commit && *blame->commit->filename) {
3725                 id = blame->commit->id;
3726                 author = blame->commit->author;
3727                 time = &blame->commit->time;
3728         }
3730         if (opt_date && draw_date(view, time))
3731                 return TRUE;
3733         if (opt_author &&
3734             draw_field(view, LINE_MAIN_AUTHOR, author, opt_author_cols, TRUE))
3735                 return TRUE;
3737         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
3738                 return TRUE;
3740         if (draw_lineno(view, lineno))
3741                 return TRUE;
3743         draw_text(view, LINE_DEFAULT, blame->text, TRUE);
3744         return TRUE;
3747 static enum request
3748 blame_request(struct view *view, enum request request, struct line *line)
3750         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3751         struct blame *blame = line->data;
3753         switch (request) {
3754         case REQ_ENTER:
3755                 if (!blame->commit) {
3756                         report("No commit loaded yet");
3757                         break;
3758                 }
3760                 if (!strcmp(blame->commit->id, NULL_ID)) {
3761                         char path[SIZEOF_STR];
3763                         if (sq_quote(path, 0, view->vid) >= sizeof(path))
3764                                 break;
3765                         string_format(opt_cmd, "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s 2>/dev/null", path);
3766                 }
3768                 open_view(view, REQ_VIEW_DIFF, flags);
3769                 break;
3771         default:
3772                 return request;
3773         }
3775         return REQ_NONE;
3778 static bool
3779 blame_grep(struct view *view, struct line *line)
3781         struct blame *blame = line->data;
3782         struct blame_commit *commit = blame->commit;
3783         regmatch_t pmatch;
3785 #define MATCH(text, on)                                                 \
3786         (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
3788         if (commit) {
3789                 char buf[DATE_COLS + 1];
3791                 if (MATCH(commit->title, 1) ||
3792                     MATCH(commit->author, opt_author) ||
3793                     MATCH(commit->id, opt_date))
3794                         return TRUE;
3796                 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
3797                     MATCH(buf, 1))
3798                         return TRUE;
3799         }
3801         return MATCH(blame->text, 1);
3803 #undef MATCH
3806 static void
3807 blame_select(struct view *view, struct line *line)
3809         struct blame *blame = line->data;
3810         struct blame_commit *commit = blame->commit;
3812         if (!commit)
3813                 return;
3815         if (!strcmp(commit->id, NULL_ID))
3816                 string_ncopy(ref_commit, "HEAD", 4);
3817         else
3818                 string_copy_rev(ref_commit, commit->id);
3821 static struct view_ops blame_ops = {
3822         "line",
3823         blame_open,
3824         blame_read,
3825         blame_draw,
3826         blame_request,
3827         blame_grep,
3828         blame_select,
3829 };
3831 /*
3832  * Status backend
3833  */
3835 struct status {
3836         char status;
3837         struct {
3838                 mode_t mode;
3839                 char rev[SIZEOF_REV];
3840                 char name[SIZEOF_STR];
3841         } old;
3842         struct {
3843                 mode_t mode;
3844                 char rev[SIZEOF_REV];
3845                 char name[SIZEOF_STR];
3846         } new;
3847 };
3849 static char status_onbranch[SIZEOF_STR];
3850 static struct status stage_status;
3851 static enum line_type stage_line_type;
3852 static size_t stage_chunks;
3853 static int *stage_chunk;
3855 /* This should work even for the "On branch" line. */
3856 static inline bool
3857 status_has_none(struct view *view, struct line *line)
3859         return line < view->line + view->lines && !line[1].data;
3862 /* Get fields from the diff line:
3863  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
3864  */
3865 static inline bool
3866 status_get_diff(struct status *file, const char *buf, size_t bufsize)
3868         const char *old_mode = buf +  1;
3869         const char *new_mode = buf +  8;
3870         const char *old_rev  = buf + 15;
3871         const char *new_rev  = buf + 56;
3872         const char *status   = buf + 97;
3874         if (bufsize < 99 ||
3875             old_mode[-1] != ':' ||
3876             new_mode[-1] != ' ' ||
3877             old_rev[-1]  != ' ' ||
3878             new_rev[-1]  != ' ' ||
3879             status[-1]   != ' ')
3880                 return FALSE;
3882         file->status = *status;
3884         string_copy_rev(file->old.rev, old_rev);
3885         string_copy_rev(file->new.rev, new_rev);
3887         file->old.mode = strtoul(old_mode, NULL, 8);
3888         file->new.mode = strtoul(new_mode, NULL, 8);
3890         file->old.name[0] = file->new.name[0] = 0;
3892         return TRUE;
3895 static bool
3896 status_run(struct view *view, const char cmd[], char status, enum line_type type)
3898         struct status *file = NULL;
3899         struct status *unmerged = NULL;
3900         char buf[SIZEOF_STR * 4];
3901         size_t bufsize = 0;
3902         FILE *pipe;
3904         pipe = popen(cmd, "r");
3905         if (!pipe)
3906                 return FALSE;
3908         add_line_data(view, NULL, type);
3910         while (!feof(pipe) && !ferror(pipe)) {
3911                 char *sep;
3912                 size_t readsize;
3914                 readsize = fread(buf + bufsize, 1, sizeof(buf) - bufsize, pipe);
3915                 if (!readsize)
3916                         break;
3917                 bufsize += readsize;
3919                 /* Process while we have NUL chars. */
3920                 while ((sep = memchr(buf, 0, bufsize))) {
3921                         size_t sepsize = sep - buf + 1;
3923                         if (!file) {
3924                                 if (!realloc_lines(view, view->line_size + 1))
3925                                         goto error_out;
3927                                 file = calloc(1, sizeof(*file));
3928                                 if (!file)
3929                                         goto error_out;
3931                                 add_line_data(view, file, type);
3932                         }
3934                         /* Parse diff info part. */
3935                         if (status) {
3936                                 file->status = status;
3937                                 if (status == 'A')
3938                                         string_copy(file->old.rev, NULL_ID);
3940                         } else if (!file->status) {
3941                                 if (!status_get_diff(file, buf, sepsize))
3942                                         goto error_out;
3944                                 bufsize -= sepsize;
3945                                 memmove(buf, sep + 1, bufsize);
3947                                 sep = memchr(buf, 0, bufsize);
3948                                 if (!sep)
3949                                         break;
3950                                 sepsize = sep - buf + 1;
3952                                 /* Collapse all 'M'odified entries that
3953                                  * follow a associated 'U'nmerged entry.
3954                                  */
3955                                 if (file->status == 'U') {
3956                                         unmerged = file;
3958                                 } else if (unmerged) {
3959                                         int collapse = !strcmp(buf, unmerged->new.name);
3961                                         unmerged = NULL;
3962                                         if (collapse) {
3963                                                 free(file);
3964                                                 view->lines--;
3965                                                 continue;
3966                                         }
3967                                 }
3968                         }
3970                         /* Grab the old name for rename/copy. */
3971                         if (!*file->old.name &&
3972                             (file->status == 'R' || file->status == 'C')) {
3973                                 sepsize = sep - buf + 1;
3974                                 string_ncopy(file->old.name, buf, sepsize);
3975                                 bufsize -= sepsize;
3976                                 memmove(buf, sep + 1, bufsize);
3978                                 sep = memchr(buf, 0, bufsize);
3979                                 if (!sep)
3980                                         break;
3981                                 sepsize = sep - buf + 1;
3982                         }
3984                         /* git-ls-files just delivers a NUL separated
3985                          * list of file names similar to the second half
3986                          * of the git-diff-* output. */
3987                         string_ncopy(file->new.name, buf, sepsize);
3988                         if (!*file->old.name)
3989                                 string_copy(file->old.name, file->new.name);
3990                         bufsize -= sepsize;
3991                         memmove(buf, sep + 1, bufsize);
3992                         file = NULL;
3993                 }
3994         }
3996         if (ferror(pipe)) {
3997 error_out:
3998                 pclose(pipe);
3999                 return FALSE;
4000         }
4002         if (!view->line[view->lines - 1].data)
4003                 add_line_data(view, NULL, LINE_STAT_NONE);
4005         pclose(pipe);
4006         return TRUE;
4009 /* Don't show unmerged entries in the staged section. */
4010 #define STATUS_DIFF_INDEX_CMD "git diff-index -z --diff-filter=ACDMRTXB --cached -M HEAD"
4011 #define STATUS_DIFF_FILES_CMD "git diff-files -z"
4012 #define STATUS_LIST_OTHER_CMD \
4013         "git ls-files -z --others --exclude-standard"
4014 #define STATUS_LIST_NO_HEAD_CMD \
4015         "git ls-files -z --cached --exclude-standard"
4017 #define STATUS_DIFF_INDEX_SHOW_CMD \
4018         "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s %s 2>/dev/null"
4020 #define STATUS_DIFF_FILES_SHOW_CMD \
4021         "git diff-files --root --patch-with-stat -C -M -- %s %s 2>/dev/null"
4023 #define STATUS_DIFF_NO_HEAD_SHOW_CMD \
4024         "git diff --no-color --patch-with-stat /dev/null %s 2>/dev/null"
4026 /* First parse staged info using git-diff-index(1), then parse unstaged
4027  * info using git-diff-files(1), and finally untracked files using
4028  * git-ls-files(1). */
4029 static bool
4030 status_open(struct view *view)
4032         unsigned long prev_lineno = view->lineno;
4034         reset_view(view);
4036         if (!realloc_lines(view, view->line_size + 7))
4037                 return FALSE;
4039         add_line_data(view, NULL, LINE_STAT_HEAD);
4040         if (opt_no_head)
4041                 string_copy(status_onbranch, "Initial commit");
4042         else if (!*opt_head)
4043                 string_copy(status_onbranch, "Not currently on any branch");
4044         else if (!string_format(status_onbranch, "On branch %s", opt_head))
4045                 return FALSE;
4047         system("git update-index -q --refresh >/dev/null 2>/dev/null");
4049         if (opt_no_head) {
4050                 if (!status_run(view, STATUS_LIST_NO_HEAD_CMD, 'A', LINE_STAT_STAGED))
4051                         return FALSE;
4052         } else if (!status_run(view, STATUS_DIFF_INDEX_CMD, 0, LINE_STAT_STAGED)) {
4053                 return FALSE;
4054         }
4056         if (!status_run(view, STATUS_DIFF_FILES_CMD, 0, LINE_STAT_UNSTAGED) ||
4057             !status_run(view, STATUS_LIST_OTHER_CMD, '?', LINE_STAT_UNTRACKED))
4058                 return FALSE;
4060         /* If all went well restore the previous line number to stay in
4061          * the context or select a line with something that can be
4062          * updated. */
4063         if (prev_lineno >= view->lines)
4064                 prev_lineno = view->lines - 1;
4065         while (prev_lineno < view->lines && !view->line[prev_lineno].data)
4066                 prev_lineno++;
4067         while (prev_lineno > 0 && !view->line[prev_lineno].data)
4068                 prev_lineno--;
4070         /* If the above fails, always skip the "On branch" line. */
4071         if (prev_lineno < view->lines)
4072                 view->lineno = prev_lineno;
4073         else
4074                 view->lineno = 1;
4076         if (view->lineno < view->offset)
4077                 view->offset = view->lineno;
4078         else if (view->offset + view->height <= view->lineno)
4079                 view->offset = view->lineno - view->height + 1;
4081         return TRUE;
4084 static bool
4085 status_draw(struct view *view, struct line *line, unsigned int lineno)
4087         struct status *status = line->data;
4088         enum line_type type;
4089         const char *text;
4091         if (!status) {
4092                 switch (line->type) {
4093                 case LINE_STAT_STAGED:
4094                         type = LINE_STAT_SECTION;
4095                         text = "Changes to be committed:";
4096                         break;
4098                 case LINE_STAT_UNSTAGED:
4099                         type = LINE_STAT_SECTION;
4100                         text = "Changed but not updated:";
4101                         break;
4103                 case LINE_STAT_UNTRACKED:
4104                         type = LINE_STAT_SECTION;
4105                         text = "Untracked files:";
4106                         break;
4108                 case LINE_STAT_NONE:
4109                         type = LINE_DEFAULT;
4110                         text = "    (no files)";
4111                         break;
4113                 case LINE_STAT_HEAD:
4114                         type = LINE_STAT_HEAD;
4115                         text = status_onbranch;
4116                         break;
4118                 default:
4119                         return FALSE;
4120                 }
4121         } else {
4122                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4124                 buf[0] = status->status;
4125                 if (draw_text(view, line->type, buf, TRUE))
4126                         return TRUE;
4127                 type = LINE_DEFAULT;
4128                 text = status->new.name;
4129         }
4131         draw_text(view, type, text, TRUE);
4132         return TRUE;
4135 static enum request
4136 status_enter(struct view *view, struct line *line)
4138         struct status *status = line->data;
4139         char oldpath[SIZEOF_STR] = "";
4140         char newpath[SIZEOF_STR] = "";
4141         const char *info;
4142         size_t cmdsize = 0;
4143         enum open_flags split;
4145         if (line->type == LINE_STAT_NONE ||
4146             (!status && line[1].type == LINE_STAT_NONE)) {
4147                 report("No file to diff");
4148                 return REQ_NONE;
4149         }
4151         if (status) {
4152                 if (sq_quote(oldpath, 0, status->old.name) >= sizeof(oldpath))
4153                         return REQ_QUIT;
4154                 /* Diffs for unmerged entries are empty when pasing the
4155                  * new path, so leave it empty. */
4156                 if (status->status != 'U' &&
4157                     sq_quote(newpath, 0, status->new.name) >= sizeof(newpath))
4158                         return REQ_QUIT;
4159         }
4161         if (opt_cdup[0] &&
4162             line->type != LINE_STAT_UNTRACKED &&
4163             !string_format_from(opt_cmd, &cmdsize, "cd %s;", opt_cdup))
4164                 return REQ_QUIT;
4166         switch (line->type) {
4167         case LINE_STAT_STAGED:
4168                 if (opt_no_head) {
4169                         if (!string_format_from(opt_cmd, &cmdsize,
4170                                                 STATUS_DIFF_NO_HEAD_SHOW_CMD,
4171                                                 newpath))
4172                                 return REQ_QUIT;
4173                 } else {
4174                         if (!string_format_from(opt_cmd, &cmdsize,
4175                                                 STATUS_DIFF_INDEX_SHOW_CMD,
4176                                                 oldpath, newpath))
4177                                 return REQ_QUIT;
4178                 }
4180                 if (status)
4181                         info = "Staged changes to %s";
4182                 else
4183                         info = "Staged changes";
4184                 break;
4186         case LINE_STAT_UNSTAGED:
4187                 if (!string_format_from(opt_cmd, &cmdsize,
4188                                         STATUS_DIFF_FILES_SHOW_CMD, oldpath, newpath))
4189                         return REQ_QUIT;
4190                 if (status)
4191                         info = "Unstaged changes to %s";
4192                 else
4193                         info = "Unstaged changes";
4194                 break;
4196         case LINE_STAT_UNTRACKED:
4197                 if (opt_pipe)
4198                         return REQ_QUIT;
4200                 if (!status) {
4201                         report("No file to show");
4202                         return REQ_NONE;
4203                 }
4205                 if (!suffixcmp(status->new.name, -1, "/")) {
4206                         report("Cannot display a directory");
4207                         return REQ_NONE;
4208                 }
4210                 opt_pipe = fopen(status->new.name, "r");
4211                 info = "Untracked file %s";
4212                 break;
4214         case LINE_STAT_HEAD:
4215                 return REQ_NONE;
4217         default:
4218                 die("line type %d not handled in switch", line->type);
4219         }
4221         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4222         open_view(view, REQ_VIEW_STAGE, OPEN_RELOAD | split);
4223         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4224                 if (status) {
4225                         stage_status = *status;
4226                 } else {
4227                         memset(&stage_status, 0, sizeof(stage_status));
4228                 }
4230                 stage_line_type = line->type;
4231                 stage_chunks = 0;
4232                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4233         }
4235         return REQ_NONE;
4238 static bool
4239 status_exists(struct status *status, enum line_type type)
4241         struct view *view = VIEW(REQ_VIEW_STATUS);
4242         struct line *line;
4244         for (line = view->line; line < view->line + view->lines; line++) {
4245                 struct status *pos = line->data;
4247                 if (line->type == type && pos &&
4248                     !strcmp(status->new.name, pos->new.name))
4249                         return TRUE;
4250         }
4252         return FALSE;
4256 static FILE *
4257 status_update_prepare(enum line_type type)
4259         char cmd[SIZEOF_STR];
4260         size_t cmdsize = 0;
4262         if (opt_cdup[0] &&
4263             type != LINE_STAT_UNTRACKED &&
4264             !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4265                 return NULL;
4267         switch (type) {
4268         case LINE_STAT_STAGED:
4269                 string_add(cmd, cmdsize, "git update-index -z --index-info");
4270                 break;
4272         case LINE_STAT_UNSTAGED:
4273         case LINE_STAT_UNTRACKED:
4274                 string_add(cmd, cmdsize, "git update-index -z --add --remove --stdin");
4275                 break;
4277         default:
4278                 die("line type %d not handled in switch", type);
4279         }
4281         return popen(cmd, "w");
4284 static bool
4285 status_update_write(FILE *pipe, struct status *status, enum line_type type)
4287         char buf[SIZEOF_STR];
4288         size_t bufsize = 0;
4289         size_t written = 0;
4291         switch (type) {
4292         case LINE_STAT_STAGED:
4293                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
4294                                         status->old.mode,
4295                                         status->old.rev,
4296                                         status->old.name, 0))
4297                         return FALSE;
4298                 break;
4300         case LINE_STAT_UNSTAGED:
4301         case LINE_STAT_UNTRACKED:
4302                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
4303                         return FALSE;
4304                 break;
4306         default:
4307                 die("line type %d not handled in switch", type);
4308         }
4310         while (!ferror(pipe) && written < bufsize) {
4311                 written += fwrite(buf + written, 1, bufsize - written, pipe);
4312         }
4314         return written == bufsize;
4317 static bool
4318 status_update_file(struct status *status, enum line_type type)
4320         FILE *pipe = status_update_prepare(type);
4321         bool result;
4323         if (!pipe)
4324                 return FALSE;
4326         result = status_update_write(pipe, status, type);
4327         pclose(pipe);
4328         return result;
4331 static bool
4332 status_update_files(struct view *view, struct line *line)
4334         FILE *pipe = status_update_prepare(line->type);
4335         bool result = TRUE;
4336         struct line *pos = view->line + view->lines;
4337         int files = 0;
4338         int file, done;
4340         if (!pipe)
4341                 return FALSE;
4343         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
4344                 files++;
4346         for (file = 0, done = 0; result && file < files; line++, file++) {
4347                 int almost_done = file * 100 / files;
4349                 if (almost_done > done) {
4350                         done = almost_done;
4351                         string_format(view->ref, "updating file %u of %u (%d%% done)",
4352                                       file, files, done);
4353                         update_view_title(view);
4354                 }
4355                 result = status_update_write(pipe, line->data, line->type);
4356         }
4358         pclose(pipe);
4359         return result;
4362 static bool
4363 status_update(struct view *view)
4365         struct line *line = &view->line[view->lineno];
4367         assert(view->lines);
4369         if (!line->data) {
4370                 /* This should work even for the "On branch" line. */
4371                 if (line < view->line + view->lines && !line[1].data) {
4372                         report("Nothing to update");
4373                         return FALSE;
4374                 }
4376                 if (!status_update_files(view, line + 1)) {
4377                         report("Failed to update file status");
4378                         return FALSE;
4379                 }
4381         } else if (!status_update_file(line->data, line->type)) {
4382                 report("Failed to update file status");
4383                 return FALSE;
4384         }
4386         return TRUE;
4389 static bool
4390 status_revert(struct status *status, enum line_type type, bool has_none)
4392         if (!status || type != LINE_STAT_UNSTAGED) {
4393                 if (type == LINE_STAT_STAGED) {
4394                         report("Cannot revert changes to staged files");
4395                 } else if (type == LINE_STAT_UNTRACKED) {
4396                         report("Cannot revert changes to untracked files");
4397                 } else if (has_none) {
4398                         report("Nothing to revert");
4399                 } else {
4400                         report("Cannot revert changes to multiple files");
4401                 }
4402                 return FALSE;
4404         } else {
4405                 char cmd[SIZEOF_STR];
4406                 char file_sq[SIZEOF_STR];
4408                 if (sq_quote(file_sq, 0, status->old.name) >= sizeof(file_sq) ||
4409                     !string_format(cmd, "git checkout -- %s%s", opt_cdup, file_sq))
4410                         return FALSE;
4412                 return run_confirm(cmd, "Are you sure you want to overwrite any changes?");
4413         }
4416 static enum request
4417 status_request(struct view *view, enum request request, struct line *line)
4419         struct status *status = line->data;
4421         switch (request) {
4422         case REQ_STATUS_UPDATE:
4423                 if (!status_update(view))
4424                         return REQ_NONE;
4425                 break;
4427         case REQ_STATUS_REVERT:
4428                 if (!status_revert(status, line->type, status_has_none(view, line)))
4429                         return REQ_NONE;
4430                 break;
4432         case REQ_STATUS_MERGE:
4433                 if (!status || status->status != 'U') {
4434                         report("Merging only possible for files with unmerged status ('U').");
4435                         return REQ_NONE;
4436                 }
4437                 open_mergetool(status->new.name);
4438                 break;
4440         case REQ_EDIT:
4441                 if (!status)
4442                         return request;
4443                 if (status->status == 'D') {
4444                         report("File has been deleted.");
4445                         return REQ_NONE;
4446                 }
4448                 open_editor(status->status != '?', status->new.name);
4449                 break;
4451         case REQ_VIEW_BLAME:
4452                 if (status) {
4453                         string_copy(opt_file, status->new.name);
4454                         opt_ref[0] = 0;
4455                 }
4456                 return request;
4458         case REQ_ENTER:
4459                 /* After returning the status view has been split to
4460                  * show the stage view. No further reloading is
4461                  * necessary. */
4462                 status_enter(view, line);
4463                 return REQ_NONE;
4465         case REQ_REFRESH:
4466                 /* Simply reload the view. */
4467                 break;
4469         default:
4470                 return request;
4471         }
4473         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
4475         return REQ_NONE;
4478 static void
4479 status_select(struct view *view, struct line *line)
4481         struct status *status = line->data;
4482         char file[SIZEOF_STR] = "all files";
4483         const char *text;
4484         const char *key;
4486         if (status && !string_format(file, "'%s'", status->new.name))
4487                 return;
4489         if (!status && line[1].type == LINE_STAT_NONE)
4490                 line++;
4492         switch (line->type) {
4493         case LINE_STAT_STAGED:
4494                 text = "Press %s to unstage %s for commit";
4495                 break;
4497         case LINE_STAT_UNSTAGED:
4498                 text = "Press %s to stage %s for commit";
4499                 break;
4501         case LINE_STAT_UNTRACKED:
4502                 text = "Press %s to stage %s for addition";
4503                 break;
4505         case LINE_STAT_HEAD:
4506         case LINE_STAT_NONE:
4507                 text = "Nothing to update";
4508                 break;
4510         default:
4511                 die("line type %d not handled in switch", line->type);
4512         }
4514         if (status && status->status == 'U') {
4515                 text = "Press %s to resolve conflict in %s";
4516                 key = get_key(REQ_STATUS_MERGE);
4518         } else {
4519                 key = get_key(REQ_STATUS_UPDATE);
4520         }
4522         string_format(view->ref, text, key, file);
4525 static bool
4526 status_grep(struct view *view, struct line *line)
4528         struct status *status = line->data;
4529         enum { S_STATUS, S_NAME, S_END } state;
4530         char buf[2] = "?";
4531         regmatch_t pmatch;
4533         if (!status)
4534                 return FALSE;
4536         for (state = S_STATUS; state < S_END; state++) {
4537                 const char *text;
4539                 switch (state) {
4540                 case S_NAME:    text = status->new.name;        break;
4541                 case S_STATUS:
4542                         buf[0] = status->status;
4543                         text = buf;
4544                         break;
4546                 default:
4547                         return FALSE;
4548                 }
4550                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4551                         return TRUE;
4552         }
4554         return FALSE;
4557 static struct view_ops status_ops = {
4558         "file",
4559         status_open,
4560         NULL,
4561         status_draw,
4562         status_request,
4563         status_grep,
4564         status_select,
4565 };
4568 static bool
4569 stage_diff_line(FILE *pipe, struct line *line)
4571         const char *buf = line->data;
4572         size_t bufsize = strlen(buf);
4573         size_t written = 0;
4575         while (!ferror(pipe) && written < bufsize) {
4576                 written += fwrite(buf + written, 1, bufsize - written, pipe);
4577         }
4579         fputc('\n', pipe);
4581         return written == bufsize;
4584 static bool
4585 stage_diff_write(FILE *pipe, struct line *line, struct line *end)
4587         while (line < end) {
4588                 if (!stage_diff_line(pipe, line++))
4589                         return FALSE;
4590                 if (line->type == LINE_DIFF_CHUNK ||
4591                     line->type == LINE_DIFF_HEADER)
4592                         break;
4593         }
4595         return TRUE;
4598 static struct line *
4599 stage_diff_find(struct view *view, struct line *line, enum line_type type)
4601         for (; view->line < line; line--)
4602                 if (line->type == type)
4603                         return line;
4605         return NULL;
4608 static bool
4609 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
4611         char cmd[SIZEOF_STR];
4612         size_t cmdsize = 0;
4613         struct line *diff_hdr;
4614         FILE *pipe;
4616         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
4617         if (!diff_hdr)
4618                 return FALSE;
4620         if (opt_cdup[0] &&
4621             !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4622                 return FALSE;
4624         if (!string_format_from(cmd, &cmdsize,
4625                                 "git apply --whitespace=nowarn %s %s - && "
4626                                 "git update-index -q --unmerged --refresh 2>/dev/null",
4627                                 revert ? "" : "--cached",
4628                                 revert || stage_line_type == LINE_STAT_STAGED ? "-R" : ""))
4629                 return FALSE;
4631         pipe = popen(cmd, "w");
4632         if (!pipe)
4633                 return FALSE;
4635         if (!stage_diff_write(pipe, diff_hdr, chunk) ||
4636             !stage_diff_write(pipe, chunk, view->line + view->lines))
4637                 chunk = NULL;
4639         pclose(pipe);
4641         return chunk ? TRUE : FALSE;
4644 static bool
4645 stage_update(struct view *view, struct line *line)
4647         struct line *chunk = NULL;
4649         if (!opt_no_head && stage_line_type != LINE_STAT_UNTRACKED)
4650                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4652         if (chunk) {
4653                 if (!stage_apply_chunk(view, chunk, FALSE)) {
4654                         report("Failed to apply chunk");
4655                         return FALSE;
4656                 }
4658         } else if (!stage_status.status) {
4659                 view = VIEW(REQ_VIEW_STATUS);
4661                 for (line = view->line; line < view->line + view->lines; line++)
4662                         if (line->type == stage_line_type)
4663                                 break;
4665                 if (!status_update_files(view, line + 1)) {
4666                         report("Failed to update files");
4667                         return FALSE;
4668                 }
4670         } else if (!status_update_file(&stage_status, stage_line_type)) {
4671                 report("Failed to update file");
4672                 return FALSE;
4673         }
4675         return TRUE;
4678 static bool
4679 stage_revert(struct view *view, struct line *line)
4681         struct line *chunk = NULL;
4683         if (!opt_no_head && stage_line_type == LINE_STAT_UNSTAGED)
4684                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4686         if (chunk) {
4687                 if (!prompt_yesno("Are you sure you want to revert changes?"))
4688                         return FALSE;
4690                 if (!stage_apply_chunk(view, chunk, TRUE)) {
4691                         report("Failed to revert chunk");
4692                         return FALSE;
4693                 }
4694                 return TRUE;
4696         } else {
4697                 return status_revert(stage_status.status ? &stage_status : NULL,
4698                                      stage_line_type, FALSE);
4699         }
4703 static void
4704 stage_next(struct view *view, struct line *line)
4706         int i;
4708         if (!stage_chunks) {
4709                 static size_t alloc = 0;
4710                 int *tmp;
4712                 for (line = view->line; line < view->line + view->lines; line++) {
4713                         if (line->type != LINE_DIFF_CHUNK)
4714                                 continue;
4716                         tmp = realloc_items(stage_chunk, &alloc,
4717                                             stage_chunks, sizeof(*tmp));
4718                         if (!tmp) {
4719                                 report("Allocation failure");
4720                                 return;
4721                         }
4723                         stage_chunk = tmp;
4724                         stage_chunk[stage_chunks++] = line - view->line;
4725                 }
4726         }
4728         for (i = 0; i < stage_chunks; i++) {
4729                 if (stage_chunk[i] > view->lineno) {
4730                         do_scroll_view(view, stage_chunk[i] - view->lineno);
4731                         report("Chunk %d of %d", i + 1, stage_chunks);
4732                         return;
4733                 }
4734         }
4736         report("No next chunk found");
4739 static enum request
4740 stage_request(struct view *view, enum request request, struct line *line)
4742         switch (request) {
4743         case REQ_STATUS_UPDATE:
4744                 if (!stage_update(view, line))
4745                         return REQ_NONE;
4746                 break;
4748         case REQ_STATUS_REVERT:
4749                 if (!stage_revert(view, line))
4750                         return REQ_NONE;
4751                 break;
4753         case REQ_STAGE_NEXT:
4754                 if (stage_line_type == LINE_STAT_UNTRACKED) {
4755                         report("File is untracked; press %s to add",
4756                                get_key(REQ_STATUS_UPDATE));
4757                         return REQ_NONE;
4758                 }
4759                 stage_next(view, line);
4760                 return REQ_NONE;
4762         case REQ_EDIT:
4763                 if (!stage_status.new.name[0])
4764                         return request;
4765                 if (stage_status.status == 'D') {
4766                         report("File has been deleted.");
4767                         return REQ_NONE;
4768                 }
4770                 open_editor(stage_status.status != '?', stage_status.new.name);
4771                 break;
4773         case REQ_REFRESH:
4774                 /* Reload everything ... */
4775                 break;
4777         case REQ_VIEW_BLAME:
4778                 if (stage_status.new.name[0]) {
4779                         string_copy(opt_file, stage_status.new.name);
4780                         opt_ref[0] = 0;
4781                 }
4782                 return request;
4784         case REQ_ENTER:
4785                 return pager_request(view, request, line);
4787         default:
4788                 return request;
4789         }
4791         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD | OPEN_NOMAXIMIZE);
4793         /* Check whether the staged entry still exists, and close the
4794          * stage view if it doesn't. */
4795         if (!status_exists(&stage_status, stage_line_type))
4796                 return REQ_VIEW_CLOSE;
4798         if (stage_line_type == LINE_STAT_UNTRACKED) {
4799                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
4800                         report("Cannot display a directory");
4801                         return REQ_NONE;
4802                 }
4804                 opt_pipe = fopen(stage_status.new.name, "r");
4805         }
4806         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
4808         return REQ_NONE;
4811 static struct view_ops stage_ops = {
4812         "line",
4813         NULL,
4814         pager_read,
4815         pager_draw,
4816         stage_request,
4817         pager_grep,
4818         pager_select,
4819 };
4822 /*
4823  * Revision graph
4824  */
4826 struct commit {
4827         char id[SIZEOF_REV];            /* SHA1 ID. */
4828         char title[128];                /* First line of the commit message. */
4829         char author[75];                /* Author of the commit. */
4830         struct tm time;                 /* Date from the author ident. */
4831         struct ref **refs;              /* Repository references. */
4832         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
4833         size_t graph_size;              /* The width of the graph array. */
4834         bool has_parents;               /* Rewritten --parents seen. */
4835 };
4837 /* Size of rev graph with no  "padding" columns */
4838 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
4840 struct rev_graph {
4841         struct rev_graph *prev, *next, *parents;
4842         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
4843         size_t size;
4844         struct commit *commit;
4845         size_t pos;
4846         unsigned int boundary:1;
4847 };
4849 /* Parents of the commit being visualized. */
4850 static struct rev_graph graph_parents[4];
4852 /* The current stack of revisions on the graph. */
4853 static struct rev_graph graph_stacks[4] = {
4854         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
4855         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
4856         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
4857         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
4858 };
4860 static inline bool
4861 graph_parent_is_merge(struct rev_graph *graph)
4863         return graph->parents->size > 1;
4866 static inline void
4867 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
4869         struct commit *commit = graph->commit;
4871         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
4872                 commit->graph[commit->graph_size++] = symbol;
4875 static void
4876 clear_rev_graph(struct rev_graph *graph)
4878         graph->boundary = 0;
4879         graph->size = graph->pos = 0;
4880         graph->commit = NULL;
4881         memset(graph->parents, 0, sizeof(*graph->parents));
4884 static void
4885 done_rev_graph(struct rev_graph *graph)
4887         if (graph_parent_is_merge(graph) &&
4888             graph->pos < graph->size - 1 &&
4889             graph->next->size == graph->size + graph->parents->size - 1) {
4890                 size_t i = graph->pos + graph->parents->size - 1;
4892                 graph->commit->graph_size = i * 2;
4893                 while (i < graph->next->size - 1) {
4894                         append_to_rev_graph(graph, ' ');
4895                         append_to_rev_graph(graph, '\\');
4896                         i++;
4897                 }
4898         }
4900         clear_rev_graph(graph);
4903 static void
4904 push_rev_graph(struct rev_graph *graph, const char *parent)
4906         int i;
4908         /* "Collapse" duplicate parents lines.
4909          *
4910          * FIXME: This needs to also update update the drawn graph but
4911          * for now it just serves as a method for pruning graph lines. */
4912         for (i = 0; i < graph->size; i++)
4913                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
4914                         return;
4916         if (graph->size < SIZEOF_REVITEMS) {
4917                 string_copy_rev(graph->rev[graph->size++], parent);
4918         }
4921 static chtype
4922 get_rev_graph_symbol(struct rev_graph *graph)
4924         chtype symbol;
4926         if (graph->boundary)
4927                 symbol = REVGRAPH_BOUND;
4928         else if (graph->parents->size == 0)
4929                 symbol = REVGRAPH_INIT;
4930         else if (graph_parent_is_merge(graph))
4931                 symbol = REVGRAPH_MERGE;
4932         else if (graph->pos >= graph->size)
4933                 symbol = REVGRAPH_BRANCH;
4934         else
4935                 symbol = REVGRAPH_COMMIT;
4937         return symbol;
4940 static void
4941 draw_rev_graph(struct rev_graph *graph)
4943         struct rev_filler {
4944                 chtype separator, line;
4945         };
4946         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
4947         static struct rev_filler fillers[] = {
4948                 { ' ',  '|' },
4949                 { '`',  '.' },
4950                 { '\'', ' ' },
4951                 { '/',  ' ' },
4952         };
4953         chtype symbol = get_rev_graph_symbol(graph);
4954         struct rev_filler *filler;
4955         size_t i;
4957         if (opt_line_graphics)
4958                 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
4960         filler = &fillers[DEFAULT];
4962         for (i = 0; i < graph->pos; i++) {
4963                 append_to_rev_graph(graph, filler->line);
4964                 if (graph_parent_is_merge(graph->prev) &&
4965                     graph->prev->pos == i)
4966                         filler = &fillers[RSHARP];
4968                 append_to_rev_graph(graph, filler->separator);
4969         }
4971         /* Place the symbol for this revision. */
4972         append_to_rev_graph(graph, symbol);
4974         if (graph->prev->size > graph->size)
4975                 filler = &fillers[RDIAG];
4976         else
4977                 filler = &fillers[DEFAULT];
4979         i++;
4981         for (; i < graph->size; i++) {
4982                 append_to_rev_graph(graph, filler->separator);
4983                 append_to_rev_graph(graph, filler->line);
4984                 if (graph_parent_is_merge(graph->prev) &&
4985                     i < graph->prev->pos + graph->parents->size)
4986                         filler = &fillers[RSHARP];
4987                 if (graph->prev->size > graph->size)
4988                         filler = &fillers[LDIAG];
4989         }
4991         if (graph->prev->size > graph->size) {
4992                 append_to_rev_graph(graph, filler->separator);
4993                 if (filler->line != ' ')
4994                         append_to_rev_graph(graph, filler->line);
4995         }
4998 /* Prepare the next rev graph */
4999 static void
5000 prepare_rev_graph(struct rev_graph *graph)
5002         size_t i;
5004         /* First, traverse all lines of revisions up to the active one. */
5005         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
5006                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
5007                         break;
5009                 push_rev_graph(graph->next, graph->rev[graph->pos]);
5010         }
5012         /* Interleave the new revision parent(s). */
5013         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
5014                 push_rev_graph(graph->next, graph->parents->rev[i]);
5016         /* Lastly, put any remaining revisions. */
5017         for (i = graph->pos + 1; i < graph->size; i++)
5018                 push_rev_graph(graph->next, graph->rev[i]);
5021 static void
5022 update_rev_graph(struct rev_graph *graph)
5024         /* If this is the finalizing update ... */
5025         if (graph->commit)
5026                 prepare_rev_graph(graph);
5028         /* Graph visualization needs a one rev look-ahead,
5029          * so the first update doesn't visualize anything. */
5030         if (!graph->prev->commit)
5031                 return;
5033         draw_rev_graph(graph->prev);
5034         done_rev_graph(graph->prev->prev);
5038 /*
5039  * Main view backend
5040  */
5042 static bool
5043 main_draw(struct view *view, struct line *line, unsigned int lineno)
5045         struct commit *commit = line->data;
5047         if (!*commit->author)
5048                 return FALSE;
5050         if (opt_date && draw_date(view, &commit->time))
5051                 return TRUE;
5053         if (opt_author &&
5054             draw_field(view, LINE_MAIN_AUTHOR, commit->author, opt_author_cols, TRUE))
5055                 return TRUE;
5057         if (opt_rev_graph && commit->graph_size &&
5058             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
5059                 return TRUE;
5061         if (opt_show_refs && commit->refs) {
5062                 size_t i = 0;
5064                 do {
5065                         enum line_type type;
5067                         if (commit->refs[i]->head)
5068                                 type = LINE_MAIN_HEAD;
5069                         else if (commit->refs[i]->ltag)
5070                                 type = LINE_MAIN_LOCAL_TAG;
5071                         else if (commit->refs[i]->tag)
5072                                 type = LINE_MAIN_TAG;
5073                         else if (commit->refs[i]->tracked)
5074                                 type = LINE_MAIN_TRACKED;
5075                         else if (commit->refs[i]->remote)
5076                                 type = LINE_MAIN_REMOTE;
5077                         else
5078                                 type = LINE_MAIN_REF;
5080                         if (draw_text(view, type, "[", TRUE) ||
5081                             draw_text(view, type, commit->refs[i]->name, TRUE) ||
5082                             draw_text(view, type, "]", TRUE))
5083                                 return TRUE;
5085                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
5086                                 return TRUE;
5087                 } while (commit->refs[i++]->next);
5088         }
5090         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
5091         return TRUE;
5094 /* Reads git log --pretty=raw output and parses it into the commit struct. */
5095 static bool
5096 main_read(struct view *view, char *line)
5098         static struct rev_graph *graph = graph_stacks;
5099         enum line_type type;
5100         struct commit *commit;
5102         if (!line) {
5103                 int i;
5105                 if (!view->lines && !view->parent)
5106                         die("No revisions match the given arguments.");
5107                 if (view->lines > 0) {
5108                         commit = view->line[view->lines - 1].data;
5109                         if (!*commit->author) {
5110                                 view->lines--;
5111                                 free(commit);
5112                                 graph->commit = NULL;
5113                         }
5114                 }
5115                 update_rev_graph(graph);
5117                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
5118                         clear_rev_graph(&graph_stacks[i]);
5119                 return TRUE;
5120         }
5122         type = get_line_type(line);
5123         if (type == LINE_COMMIT) {
5124                 commit = calloc(1, sizeof(struct commit));
5125                 if (!commit)
5126                         return FALSE;
5128                 line += STRING_SIZE("commit ");
5129                 if (*line == '-') {
5130                         graph->boundary = 1;
5131                         line++;
5132                 }
5134                 string_copy_rev(commit->id, line);
5135                 commit->refs = get_refs(commit->id);
5136                 graph->commit = commit;
5137                 add_line_data(view, commit, LINE_MAIN_COMMIT);
5139                 while ((line = strchr(line, ' '))) {
5140                         line++;
5141                         push_rev_graph(graph->parents, line);
5142                         commit->has_parents = TRUE;
5143                 }
5144                 return TRUE;
5145         }
5147         if (!view->lines)
5148                 return TRUE;
5149         commit = view->line[view->lines - 1].data;
5151         switch (type) {
5152         case LINE_PARENT:
5153                 if (commit->has_parents)
5154                         break;
5155                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5156                 break;
5158         case LINE_AUTHOR:
5159         {
5160                 /* Parse author lines where the name may be empty:
5161                  *      author  <email@address.tld> 1138474660 +0100
5162                  */
5163                 char *ident = line + STRING_SIZE("author ");
5164                 char *nameend = strchr(ident, '<');
5165                 char *emailend = strchr(ident, '>');
5167                 if (!nameend || !emailend)
5168                         break;
5170                 update_rev_graph(graph);
5171                 graph = graph->next;
5173                 *nameend = *emailend = 0;
5174                 ident = chomp_string(ident);
5175                 if (!*ident) {
5176                         ident = chomp_string(nameend + 1);
5177                         if (!*ident)
5178                                 ident = "Unknown";
5179                 }
5181                 string_ncopy(commit->author, ident, strlen(ident));
5183                 /* Parse epoch and timezone */
5184                 if (emailend[1] == ' ') {
5185                         char *secs = emailend + 2;
5186                         char *zone = strchr(secs, ' ');
5187                         time_t time = (time_t) atol(secs);
5189                         if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
5190                                 long tz;
5192                                 zone++;
5193                                 tz  = ('0' - zone[1]) * 60 * 60 * 10;
5194                                 tz += ('0' - zone[2]) * 60 * 60;
5195                                 tz += ('0' - zone[3]) * 60;
5196                                 tz += ('0' - zone[4]) * 60;
5198                                 if (zone[0] == '-')
5199                                         tz = -tz;
5201                                 time -= tz;
5202                         }
5204                         gmtime_r(&time, &commit->time);
5205                 }
5206                 break;
5207         }
5208         default:
5209                 /* Fill in the commit title if it has not already been set. */
5210                 if (commit->title[0])
5211                         break;
5213                 /* Require titles to start with a non-space character at the
5214                  * offset used by git log. */
5215                 if (strncmp(line, "    ", 4))
5216                         break;
5217                 line += 4;
5218                 /* Well, if the title starts with a whitespace character,
5219                  * try to be forgiving.  Otherwise we end up with no title. */
5220                 while (isspace(*line))
5221                         line++;
5222                 if (*line == '\0')
5223                         break;
5224                 /* FIXME: More graceful handling of titles; append "..." to
5225                  * shortened titles, etc. */
5227                 string_ncopy(commit->title, line, strlen(line));
5228         }
5230         return TRUE;
5233 static enum request
5234 main_request(struct view *view, enum request request, struct line *line)
5236         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5238         switch (request) {
5239         case REQ_ENTER:
5240                 open_view(view, REQ_VIEW_DIFF, flags);
5241                 break;
5242         case REQ_REFRESH:
5243                 load_refs();
5244                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
5245                 break;
5246         default:
5247                 return request;
5248         }
5250         return REQ_NONE;
5253 static bool
5254 grep_refs(struct ref **refs, regex_t *regex)
5256         regmatch_t pmatch;
5257         size_t i = 0;
5259         if (!refs)
5260                 return FALSE;
5261         do {
5262                 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
5263                         return TRUE;
5264         } while (refs[i++]->next);
5266         return FALSE;
5269 static bool
5270 main_grep(struct view *view, struct line *line)
5272         struct commit *commit = line->data;
5273         enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
5274         char buf[DATE_COLS + 1];
5275         regmatch_t pmatch;
5277         for (state = S_TITLE; state < S_END; state++) {
5278                 char *text;
5280                 switch (state) {
5281                 case S_TITLE:   text = commit->title;   break;
5282                 case S_AUTHOR:
5283                         if (!opt_author)
5284                                 continue;
5285                         text = commit->author;
5286                         break;
5287                 case S_DATE:
5288                         if (!opt_date)
5289                                 continue;
5290                         if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5291                                 continue;
5292                         text = buf;
5293                         break;
5294                 case S_REFS:
5295                         if (!opt_show_refs)
5296                                 continue;
5297                         if (grep_refs(commit->refs, view->regex) == TRUE)
5298                                 return TRUE;
5299                         continue;
5300                 default:
5301                         return FALSE;
5302                 }
5304                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5305                         return TRUE;
5306         }
5308         return FALSE;
5311 static void
5312 main_select(struct view *view, struct line *line)
5314         struct commit *commit = line->data;
5316         string_copy_rev(view->ref, commit->id);
5317         string_copy_rev(ref_commit, view->ref);
5320 static struct view_ops main_ops = {
5321         "commit",
5322         NULL,
5323         main_read,
5324         main_draw,
5325         main_request,
5326         main_grep,
5327         main_select,
5328 };
5331 /*
5332  * Unicode / UTF-8 handling
5333  *
5334  * NOTE: Much of the following code for dealing with unicode is derived from
5335  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
5336  * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
5337  */
5339 /* I've (over)annotated a lot of code snippets because I am not entirely
5340  * confident that the approach taken by this small UTF-8 interface is correct.
5341  * --jonas */
5343 static inline int
5344 unicode_width(unsigned long c)
5346         if (c >= 0x1100 &&
5347            (c <= 0x115f                         /* Hangul Jamo */
5348             || c == 0x2329
5349             || c == 0x232a
5350             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
5351                                                 /* CJK ... Yi */
5352             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
5353             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
5354             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
5355             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
5356             || (c >= 0xffe0  && c <= 0xffe6)
5357             || (c >= 0x20000 && c <= 0x2fffd)
5358             || (c >= 0x30000 && c <= 0x3fffd)))
5359                 return 2;
5361         if (c == '\t')
5362                 return opt_tab_size;
5364         return 1;
5367 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
5368  * Illegal bytes are set one. */
5369 static const unsigned char utf8_bytes[256] = {
5370         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,
5371         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,
5372         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,
5373         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,
5374         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,
5375         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,
5376         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,
5377         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,
5378 };
5380 /* Decode UTF-8 multi-byte representation into a unicode character. */
5381 static inline unsigned long
5382 utf8_to_unicode(const char *string, size_t length)
5384         unsigned long unicode;
5386         switch (length) {
5387         case 1:
5388                 unicode  =   string[0];
5389                 break;
5390         case 2:
5391                 unicode  =  (string[0] & 0x1f) << 6;
5392                 unicode +=  (string[1] & 0x3f);
5393                 break;
5394         case 3:
5395                 unicode  =  (string[0] & 0x0f) << 12;
5396                 unicode += ((string[1] & 0x3f) << 6);
5397                 unicode +=  (string[2] & 0x3f);
5398                 break;
5399         case 4:
5400                 unicode  =  (string[0] & 0x0f) << 18;
5401                 unicode += ((string[1] & 0x3f) << 12);
5402                 unicode += ((string[2] & 0x3f) << 6);
5403                 unicode +=  (string[3] & 0x3f);
5404                 break;
5405         case 5:
5406                 unicode  =  (string[0] & 0x0f) << 24;
5407                 unicode += ((string[1] & 0x3f) << 18);
5408                 unicode += ((string[2] & 0x3f) << 12);
5409                 unicode += ((string[3] & 0x3f) << 6);
5410                 unicode +=  (string[4] & 0x3f);
5411                 break;
5412         case 6:
5413                 unicode  =  (string[0] & 0x01) << 30;
5414                 unicode += ((string[1] & 0x3f) << 24);
5415                 unicode += ((string[2] & 0x3f) << 18);
5416                 unicode += ((string[3] & 0x3f) << 12);
5417                 unicode += ((string[4] & 0x3f) << 6);
5418                 unicode +=  (string[5] & 0x3f);
5419                 break;
5420         default:
5421                 die("Invalid unicode length");
5422         }
5424         /* Invalid characters could return the special 0xfffd value but NUL
5425          * should be just as good. */
5426         return unicode > 0xffff ? 0 : unicode;
5429 /* Calculates how much of string can be shown within the given maximum width
5430  * and sets trimmed parameter to non-zero value if all of string could not be
5431  * shown. If the reserve flag is TRUE, it will reserve at least one
5432  * trailing character, which can be useful when drawing a delimiter.
5433  *
5434  * Returns the number of bytes to output from string to satisfy max_width. */
5435 static size_t
5436 utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve)
5438         const char *start = string;
5439         const char *end = strchr(string, '\0');
5440         unsigned char last_bytes = 0;
5441         size_t last_ucwidth = 0;
5443         *width = 0;
5444         *trimmed = 0;
5446         while (string < end) {
5447                 int c = *(unsigned char *) string;
5448                 unsigned char bytes = utf8_bytes[c];
5449                 size_t ucwidth;
5450                 unsigned long unicode;
5452                 if (string + bytes > end)
5453                         break;
5455                 /* Change representation to figure out whether
5456                  * it is a single- or double-width character. */
5458                 unicode = utf8_to_unicode(string, bytes);
5459                 /* FIXME: Graceful handling of invalid unicode character. */
5460                 if (!unicode)
5461                         break;
5463                 ucwidth = unicode_width(unicode);
5464                 *width  += ucwidth;
5465                 if (*width > max_width) {
5466                         *trimmed = 1;
5467                         *width -= ucwidth;
5468                         if (reserve && *width == max_width) {
5469                                 string -= last_bytes;
5470                                 *width -= last_ucwidth;
5471                         }
5472                         break;
5473                 }
5475                 string  += bytes;
5476                 last_bytes = bytes;
5477                 last_ucwidth = ucwidth;
5478         }
5480         return string - start;
5484 /*
5485  * Status management
5486  */
5488 /* Whether or not the curses interface has been initialized. */
5489 static bool cursed = FALSE;
5491 /* The status window is used for polling keystrokes. */
5492 static WINDOW *status_win;
5494 static bool status_empty = TRUE;
5496 /* Update status and title window. */
5497 static void
5498 report(const char *msg, ...)
5500         struct view *view = display[current_view];
5502         if (input_mode)
5503                 return;
5505         if (!view) {
5506                 char buf[SIZEOF_STR];
5507                 va_list args;
5509                 va_start(args, msg);
5510                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
5511                         buf[sizeof(buf) - 1] = 0;
5512                         buf[sizeof(buf) - 2] = '.';
5513                         buf[sizeof(buf) - 3] = '.';
5514                         buf[sizeof(buf) - 4] = '.';
5515                 }
5516                 va_end(args);
5517                 die("%s", buf);
5518         }
5520         if (!status_empty || *msg) {
5521                 va_list args;
5523                 va_start(args, msg);
5525                 wmove(status_win, 0, 0);
5526                 if (*msg) {
5527                         vwprintw(status_win, msg, args);
5528                         status_empty = FALSE;
5529                 } else {
5530                         status_empty = TRUE;
5531                 }
5532                 wclrtoeol(status_win);
5533                 wrefresh(status_win);
5535                 va_end(args);
5536         }
5538         update_view_title(view);
5539         update_display_cursor(view);
5542 /* Controls when nodelay should be in effect when polling user input. */
5543 static void
5544 set_nonblocking_input(bool loading)
5546         static unsigned int loading_views;
5548         if ((loading == FALSE && loading_views-- == 1) ||
5549             (loading == TRUE  && loading_views++ == 0))
5550                 nodelay(status_win, loading);
5553 static void
5554 init_display(void)
5556         int x, y;
5558         /* Initialize the curses library */
5559         if (isatty(STDIN_FILENO)) {
5560                 cursed = !!initscr();
5561                 opt_tty = stdin;
5562         } else {
5563                 /* Leave stdin and stdout alone when acting as a pager. */
5564                 opt_tty = fopen("/dev/tty", "r+");
5565                 if (!opt_tty)
5566                         die("Failed to open /dev/tty");
5567                 cursed = !!newterm(NULL, opt_tty, opt_tty);
5568         }
5570         if (!cursed)
5571                 die("Failed to initialize curses");
5573         nonl();         /* Tell curses not to do NL->CR/NL on output */
5574         cbreak();       /* Take input chars one at a time, no wait for \n */
5575         noecho();       /* Don't echo input */
5576         leaveok(stdscr, TRUE);
5578         if (has_colors())
5579                 init_colors();
5581         getmaxyx(stdscr, y, x);
5582         status_win = newwin(1, 0, y - 1, 0);
5583         if (!status_win)
5584                 die("Failed to create status window");
5586         /* Enable keyboard mapping */
5587         keypad(status_win, TRUE);
5588         wbkgdset(status_win, get_line_attr(LINE_STATUS));
5590         TABSIZE = opt_tab_size;
5591         if (opt_line_graphics) {
5592                 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
5593         }
5596 static bool
5597 prompt_yesno(const char *prompt)
5599         enum { WAIT, STOP, CANCEL  } status = WAIT;
5600         bool answer = FALSE;
5602         while (status == WAIT) {
5603                 struct view *view;
5604                 int i, key;
5606                 input_mode = TRUE;
5608                 foreach_view (view, i)
5609                         update_view(view);
5611                 input_mode = FALSE;
5613                 mvwprintw(status_win, 0, 0, "%s [Yy]/[Nn]", prompt);
5614                 wclrtoeol(status_win);
5616                 /* Refresh, accept single keystroke of input */
5617                 key = wgetch(status_win);
5618                 switch (key) {
5619                 case ERR:
5620                         break;
5622                 case 'y':
5623                 case 'Y':
5624                         answer = TRUE;
5625                         status = STOP;
5626                         break;
5628                 case KEY_ESC:
5629                 case KEY_RETURN:
5630                 case KEY_ENTER:
5631                 case KEY_BACKSPACE:
5632                 case 'n':
5633                 case 'N':
5634                 case '\n':
5635                 default:
5636                         answer = FALSE;
5637                         status = CANCEL;
5638                 }
5639         }
5641         /* Clear the status window */
5642         status_empty = FALSE;
5643         report("");
5645         return answer;
5648 static char *
5649 read_prompt(const char *prompt)
5651         enum { READING, STOP, CANCEL } status = READING;
5652         static char buf[sizeof(opt_cmd) - STRING_SIZE("git \0")];
5653         int pos = 0;
5655         while (status == READING) {
5656                 struct view *view;
5657                 int i, key;
5659                 input_mode = TRUE;
5661                 foreach_view (view, i)
5662                         update_view(view);
5664                 input_mode = FALSE;
5666                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
5667                 wclrtoeol(status_win);
5669                 /* Refresh, accept single keystroke of input */
5670                 key = wgetch(status_win);
5671                 switch (key) {
5672                 case KEY_RETURN:
5673                 case KEY_ENTER:
5674                 case '\n':
5675                         status = pos ? STOP : CANCEL;
5676                         break;
5678                 case KEY_BACKSPACE:
5679                         if (pos > 0)
5680                                 pos--;
5681                         else
5682                                 status = CANCEL;
5683                         break;
5685                 case KEY_ESC:
5686                         status = CANCEL;
5687                         break;
5689                 case ERR:
5690                         break;
5692                 default:
5693                         if (pos >= sizeof(buf)) {
5694                                 report("Input string too long");
5695                                 return NULL;
5696                         }
5698                         if (isprint(key))
5699                                 buf[pos++] = (char) key;
5700                 }
5701         }
5703         /* Clear the status window */
5704         status_empty = FALSE;
5705         report("");
5707         if (status == CANCEL)
5708                 return NULL;
5710         buf[pos++] = 0;
5712         return buf;
5715 /*
5716  * Repository references
5717  */
5719 static struct ref *refs = NULL;
5720 static size_t refs_alloc = 0;
5721 static size_t refs_size = 0;
5723 /* Id <-> ref store */
5724 static struct ref ***id_refs = NULL;
5725 static size_t id_refs_alloc = 0;
5726 static size_t id_refs_size = 0;
5728 static int
5729 compare_refs(const void *ref1_, const void *ref2_)
5731         const struct ref *ref1 = *(const struct ref **)ref1_;
5732         const struct ref *ref2 = *(const struct ref **)ref2_;
5734         if (ref1->tag != ref2->tag)
5735                 return ref2->tag - ref1->tag;
5736         if (ref1->ltag != ref2->ltag)
5737                 return ref2->ltag - ref2->ltag;
5738         if (ref1->head != ref2->head)
5739                 return ref2->head - ref1->head;
5740         if (ref1->tracked != ref2->tracked)
5741                 return ref2->tracked - ref1->tracked;
5742         if (ref1->remote != ref2->remote)
5743                 return ref2->remote - ref1->remote;
5744         return strcmp(ref1->name, ref2->name);
5747 static struct ref **
5748 get_refs(const char *id)
5750         struct ref ***tmp_id_refs;
5751         struct ref **ref_list = NULL;
5752         size_t ref_list_alloc = 0;
5753         size_t ref_list_size = 0;
5754         size_t i;
5756         for (i = 0; i < id_refs_size; i++)
5757                 if (!strcmp(id, id_refs[i][0]->id))
5758                         return id_refs[i];
5760         tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
5761                                     sizeof(*id_refs));
5762         if (!tmp_id_refs)
5763                 return NULL;
5765         id_refs = tmp_id_refs;
5767         for (i = 0; i < refs_size; i++) {
5768                 struct ref **tmp;
5770                 if (strcmp(id, refs[i].id))
5771                         continue;
5773                 tmp = realloc_items(ref_list, &ref_list_alloc,
5774                                     ref_list_size + 1, sizeof(*ref_list));
5775                 if (!tmp) {
5776                         if (ref_list)
5777                                 free(ref_list);
5778                         return NULL;
5779                 }
5781                 ref_list = tmp;
5782                 ref_list[ref_list_size] = &refs[i];
5783                 /* XXX: The properties of the commit chains ensures that we can
5784                  * safely modify the shared ref. The repo references will
5785                  * always be similar for the same id. */
5786                 ref_list[ref_list_size]->next = 1;
5788                 ref_list_size++;
5789         }
5791         if (ref_list) {
5792                 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
5793                 ref_list[ref_list_size - 1]->next = 0;
5794                 id_refs[id_refs_size++] = ref_list;
5795         }
5797         return ref_list;
5800 static int
5801 read_ref(char *id, size_t idlen, char *name, size_t namelen)
5803         struct ref *ref;
5804         bool tag = FALSE;
5805         bool ltag = FALSE;
5806         bool remote = FALSE;
5807         bool tracked = FALSE;
5808         bool check_replace = FALSE;
5809         bool head = FALSE;
5811         if (!prefixcmp(name, "refs/tags/")) {
5812                 if (!suffixcmp(name, namelen, "^{}")) {
5813                         namelen -= 3;
5814                         name[namelen] = 0;
5815                         if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
5816                                 check_replace = TRUE;
5817                 } else {
5818                         ltag = TRUE;
5819                 }
5821                 tag = TRUE;
5822                 namelen -= STRING_SIZE("refs/tags/");
5823                 name    += STRING_SIZE("refs/tags/");
5825         } else if (!prefixcmp(name, "refs/remotes/")) {
5826                 remote = TRUE;
5827                 namelen -= STRING_SIZE("refs/remotes/");
5828                 name    += STRING_SIZE("refs/remotes/");
5829                 tracked  = !strcmp(opt_remote, name);
5831         } else if (!prefixcmp(name, "refs/heads/")) {
5832                 namelen -= STRING_SIZE("refs/heads/");
5833                 name    += STRING_SIZE("refs/heads/");
5834                 head     = !strncmp(opt_head, name, namelen);
5836         } else if (!strcmp(name, "HEAD")) {
5837                 opt_no_head = FALSE;
5838                 return OK;
5839         }
5841         if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
5842                 /* it's an annotated tag, replace the previous sha1 with the
5843                  * resolved commit id; relies on the fact git-ls-remote lists
5844                  * the commit id of an annotated tag right before the commit id
5845                  * it points to. */
5846                 refs[refs_size - 1].ltag = ltag;
5847                 string_copy_rev(refs[refs_size - 1].id, id);
5849                 return OK;
5850         }
5851         refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
5852         if (!refs)
5853                 return ERR;
5855         ref = &refs[refs_size++];
5856         ref->name = malloc(namelen + 1);
5857         if (!ref->name)
5858                 return ERR;
5860         strncpy(ref->name, name, namelen);
5861         ref->name[namelen] = 0;
5862         ref->head = head;
5863         ref->tag = tag;
5864         ref->ltag = ltag;
5865         ref->remote = remote;
5866         ref->tracked = tracked;
5867         string_copy_rev(ref->id, id);
5869         return OK;
5872 static int
5873 load_refs(void)
5875         const char *cmd_env = getenv("TIG_LS_REMOTE");
5876         const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
5878         if (!*opt_git_dir)
5879                 return OK;
5881         while (refs_size > 0)
5882                 free(refs[--refs_size].name);
5883         while (id_refs_size > 0)
5884                 free(id_refs[--id_refs_size]);
5886         return read_properties(popen(cmd, "r"), "\t", read_ref);
5889 static int
5890 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
5892         if (!strcmp(name, "i18n.commitencoding"))
5893                 string_ncopy(opt_encoding, value, valuelen);
5895         if (!strcmp(name, "core.editor"))
5896                 string_ncopy(opt_editor, value, valuelen);
5898         /* branch.<head>.remote */
5899         if (*opt_head &&
5900             !strncmp(name, "branch.", 7) &&
5901             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
5902             !strcmp(name + 7 + strlen(opt_head), ".remote"))
5903                 string_ncopy(opt_remote, value, valuelen);
5905         if (*opt_head && *opt_remote &&
5906             !strncmp(name, "branch.", 7) &&
5907             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
5908             !strcmp(name + 7 + strlen(opt_head), ".merge")) {
5909                 size_t from = strlen(opt_remote);
5911                 if (!prefixcmp(value, "refs/heads/")) {
5912                         value += STRING_SIZE("refs/heads/");
5913                         valuelen -= STRING_SIZE("refs/heads/");
5914                 }
5916                 if (!string_format_from(opt_remote, &from, "/%s", value))
5917                         opt_remote[0] = 0;
5918         }
5920         return OK;
5923 static int
5924 load_git_config(void)
5926         return read_properties(popen("git " GIT_CONFIG " --list", "r"),
5927                                "=", read_repo_config_option);
5930 static int
5931 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
5933         if (!opt_git_dir[0]) {
5934                 string_ncopy(opt_git_dir, name, namelen);
5936         } else if (opt_is_inside_work_tree == -1) {
5937                 /* This can be 3 different values depending on the
5938                  * version of git being used. If git-rev-parse does not
5939                  * understand --is-inside-work-tree it will simply echo
5940                  * the option else either "true" or "false" is printed.
5941                  * Default to true for the unknown case. */
5942                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
5944         } else if (opt_cdup[0] == ' ') {
5945                 string_ncopy(opt_cdup, name, namelen);
5946         } else {
5947                 if (!prefixcmp(name, "refs/heads/")) {
5948                         namelen -= STRING_SIZE("refs/heads/");
5949                         name    += STRING_SIZE("refs/heads/");
5950                         string_ncopy(opt_head, name, namelen);
5951                 }
5952         }
5954         return OK;
5957 static int
5958 load_repo_info(void)
5960         int result;
5961         FILE *pipe = popen("(git rev-parse --git-dir --is-inside-work-tree "
5962                            " --show-cdup; git symbolic-ref HEAD) 2>/dev/null", "r");
5964         /* XXX: The line outputted by "--show-cdup" can be empty so
5965          * initialize it to something invalid to make it possible to
5966          * detect whether it has been set or not. */
5967         opt_cdup[0] = ' ';
5969         result = read_properties(pipe, "=", read_repo_info);
5970         if (opt_cdup[0] == ' ')
5971                 opt_cdup[0] = 0;
5973         return result;
5976 static int
5977 read_properties(FILE *pipe, const char *separators,
5978                 int (*read_property)(char *, size_t, char *, size_t))
5980         char buffer[BUFSIZ];
5981         char *name;
5982         int state = OK;
5984         if (!pipe)
5985                 return ERR;
5987         while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
5988                 char *value;
5989                 size_t namelen;
5990                 size_t valuelen;
5992                 name = chomp_string(name);
5993                 namelen = strcspn(name, separators);
5995                 if (name[namelen]) {
5996                         name[namelen] = 0;
5997                         value = chomp_string(name + namelen + 1);
5998                         valuelen = strlen(value);
6000                 } else {
6001                         value = "";
6002                         valuelen = 0;
6003                 }
6005                 state = read_property(name, namelen, value, valuelen);
6006         }
6008         if (state != ERR && ferror(pipe))
6009                 state = ERR;
6011         pclose(pipe);
6013         return state;
6017 /*
6018  * Main
6019  */
6021 static void __NORETURN
6022 quit(int sig)
6024         /* XXX: Restore tty modes and let the OS cleanup the rest! */
6025         if (cursed)
6026                 endwin();
6027         exit(0);
6030 static void __NORETURN
6031 die(const char *err, ...)
6033         va_list args;
6035         endwin();
6037         va_start(args, err);
6038         fputs("tig: ", stderr);
6039         vfprintf(stderr, err, args);
6040         fputs("\n", stderr);
6041         va_end(args);
6043         exit(1);
6046 static void
6047 warn(const char *msg, ...)
6049         va_list args;
6051         va_start(args, msg);
6052         fputs("tig warning: ", stderr);
6053         vfprintf(stderr, msg, args);
6054         fputs("\n", stderr);
6055         va_end(args);
6058 int
6059 main(int argc, const char *argv[])
6061         struct view *view;
6062         enum request request;
6063         size_t i;
6065         signal(SIGINT, quit);
6067         if (setlocale(LC_ALL, "")) {
6068                 char *codeset = nl_langinfo(CODESET);
6070                 string_ncopy(opt_codeset, codeset, strlen(codeset));
6071         }
6073         if (load_repo_info() == ERR)
6074                 die("Failed to load repo info.");
6076         if (load_options() == ERR)
6077                 die("Failed to load user config.");
6079         if (load_git_config() == ERR)
6080                 die("Failed to load repo config.");
6082         request = parse_options(argc, argv);
6083         if (request == REQ_NONE)
6084                 return 0;
6086         /* Require a git repository unless when running in pager mode. */
6087         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
6088                 die("Not a git repository");
6090         if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
6091                 opt_utf8 = FALSE;
6093         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
6094                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
6095                 if (opt_iconv == ICONV_NONE)
6096                         die("Failed to initialize character set conversion");
6097         }
6099         if (load_refs() == ERR)
6100                 die("Failed to load refs.");
6102         foreach_view (view, i)
6103                 view->cmd_env = getenv(view->cmd_env);
6105         init_display();
6107         while (view_driver(display[current_view], request)) {
6108                 int key;
6109                 int i;
6111                 foreach_view (view, i)
6112                         update_view(view);
6113                 view = display[current_view];
6115                 /* Refresh, accept single keystroke of input */
6116                 key = wgetch(status_win);
6118                 /* wgetch() with nodelay() enabled returns ERR when there's no
6119                  * input. */
6120                 if (key == ERR) {
6121                         request = REQ_NONE;
6122                         continue;
6123                 }
6125                 request = get_keybinding(view->keymap, key);
6127                 /* Some low-level request handling. This keeps access to
6128                  * status_win restricted. */
6129                 switch (request) {
6130                 case REQ_PROMPT:
6131                 {
6132                         char *cmd = read_prompt(":");
6134                         if (cmd && string_format(opt_cmd, "git %s", cmd)) {
6135                                 if (strncmp(cmd, "show", 4) && isspace(cmd[4])) {
6136                                         request = REQ_VIEW_DIFF;
6137                                 } else {
6138                                         request = REQ_VIEW_PAGER;
6139                                 }
6141                                 /* Always reload^Wrerun commands from the prompt. */
6142                                 open_view(view, request, OPEN_RELOAD);
6143                         }
6145                         request = REQ_NONE;
6146                         break;
6147                 }
6148                 case REQ_SEARCH:
6149                 case REQ_SEARCH_BACK:
6150                 {
6151                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
6152                         char *search = read_prompt(prompt);
6154                         if (search)
6155                                 string_ncopy(opt_search, search, strlen(search));
6156                         else
6157                                 request = REQ_NONE;
6158                         break;
6159                 }
6160                 case REQ_SCREEN_RESIZE:
6161                 {
6162                         int height, width;
6164                         getmaxyx(stdscr, height, width);
6166                         /* Resize the status view and let the view driver take
6167                          * care of resizing the displayed views. */
6168                         wresize(status_win, 1, width);
6169                         mvwin(status_win, height - 1, 0);
6170                         wrefresh(status_win);
6171                         break;
6172                 }
6173                 default:
6174                         break;
6175                 }
6176         }
6178         quit(0);
6180         return 0;