Code

0360e173457a455d4d266b46ee338ac19e4fefba
[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);
72 #define ABS(x)          ((x) >= 0  ? (x) : -(x))
73 #define MIN(x, y)       ((x) < (y) ? (x) :  (y))
75 #define ARRAY_SIZE(x)   (sizeof(x) / sizeof(x[0]))
76 #define STRING_SIZE(x)  (sizeof(x) - 1)
78 #define SIZEOF_STR      1024    /* Default string size. */
79 #define SIZEOF_REF      256     /* Size of symbolic or SHA1 ID. */
80 #define SIZEOF_REV      41      /* Holds a SHA-1 and an ending NUL */
82 /* Revision graph */
84 #define REVGRAPH_INIT   'I'
85 #define REVGRAPH_MERGE  'M'
86 #define REVGRAPH_BRANCH '+'
87 #define REVGRAPH_COMMIT '*'
88 #define REVGRAPH_BOUND  '^'
90 #define SIZEOF_REVGRAPH 19      /* Size of revision ancestry graphics. */
92 /* This color name can be used to refer to the default term colors. */
93 #define COLOR_DEFAULT   (-1)
95 #define ICONV_NONE      ((iconv_t) -1)
96 #ifndef ICONV_CONST
97 #define ICONV_CONST     /* nothing */
98 #endif
100 /* The format and size of the date column in the main view. */
101 #define DATE_FORMAT     "%Y-%m-%d %H:%M"
102 #define DATE_COLS       STRING_SIZE("2006-04-29 14:21 ")
104 #define AUTHOR_COLS     20
105 #define ID_COLS         8
107 /* The default interval between line numbers. */
108 #define NUMBER_INTERVAL 5
110 #define TAB_SIZE        8
112 #define SCALE_SPLIT_VIEW(height)        ((height) * 2 / 3)
114 #define NULL_ID         "0000000000000000000000000000000000000000"
116 #ifndef GIT_CONFIG
117 #define GIT_CONFIG "git config"
118 #endif
120 #define TIG_LS_REMOTE \
121         "git ls-remote . 2>/dev/null"
123 #define TIG_DIFF_CMD \
124         "git show --pretty=fuller --no-color --root --patch-with-stat --find-copies-harder -C %s 2>/dev/null"
126 #define TIG_LOG_CMD     \
127         "git log --no-color --cc --stat -n100 %s 2>/dev/null"
129 #define TIG_MAIN_BASE \
130         "git log --no-color --pretty=raw --parents --topo-order"
132 #define TIG_MAIN_CMD \
133         TIG_MAIN_BASE " %s 2>/dev/null"
135 #define TIG_TREE_CMD    \
136         "git ls-tree %s %s"
138 #define TIG_BLOB_CMD    \
139         "git cat-file blob %s"
141 /* XXX: Needs to be defined to the empty string. */
142 #define TIG_HELP_CMD    ""
143 #define TIG_PAGER_CMD   ""
144 #define TIG_STATUS_CMD  ""
145 #define TIG_STAGE_CMD   ""
146 #define TIG_BLAME_CMD   ""
148 /* Some ascii-shorthands fitted into the ncurses namespace. */
149 #define KEY_TAB         '\t'
150 #define KEY_RETURN      '\r'
151 #define KEY_ESC         27
154 struct ref {
155         char *name;             /* Ref name; tag or head names are shortened. */
156         char id[SIZEOF_REV];    /* Commit SHA1 ID */
157         unsigned int head:1;    /* Is it the current HEAD? */
158         unsigned int tag:1;     /* Is it a tag? */
159         unsigned int ltag:1;    /* If so, is the tag local? */
160         unsigned int remote:1;  /* Is it a remote ref? */
161         unsigned int tracked:1; /* Is it the remote for the current HEAD? */
162         unsigned int next:1;    /* For ref lists: are there more refs? */
163 };
165 static struct ref **get_refs(char *id);
167 struct int_map {
168         const char *name;
169         int namelen;
170         int value;
171 };
173 static int
174 set_from_int_map(struct int_map *map, size_t map_size,
175                  int *value, const char *name, int namelen)
178         int i;
180         for (i = 0; i < map_size; i++)
181                 if (namelen == map[i].namelen &&
182                     !strncasecmp(name, map[i].name, namelen)) {
183                         *value = map[i].value;
184                         return OK;
185                 }
187         return ERR;
191 /*
192  * String helpers
193  */
195 static inline void
196 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
198         if (srclen > dstlen - 1)
199                 srclen = dstlen - 1;
201         strncpy(dst, src, srclen);
202         dst[srclen] = 0;
205 /* Shorthands for safely copying into a fixed buffer. */
207 #define string_copy(dst, src) \
208         string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
210 #define string_ncopy(dst, src, srclen) \
211         string_ncopy_do(dst, sizeof(dst), src, srclen)
213 #define string_copy_rev(dst, src) \
214         string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
216 #define string_add(dst, from, src) \
217         string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
219 static char *
220 chomp_string(char *name)
222         int namelen;
224         while (isspace(*name))
225                 name++;
227         namelen = strlen(name) - 1;
228         while (namelen > 0 && isspace(name[namelen]))
229                 name[namelen--] = 0;
231         return name;
234 static bool
235 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
237         va_list args;
238         size_t pos = bufpos ? *bufpos : 0;
240         va_start(args, fmt);
241         pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
242         va_end(args);
244         if (bufpos)
245                 *bufpos = pos;
247         return pos >= bufsize ? FALSE : TRUE;
250 #define string_format(buf, fmt, args...) \
251         string_nformat(buf, sizeof(buf), NULL, fmt, args)
253 #define string_format_from(buf, from, fmt, args...) \
254         string_nformat(buf, sizeof(buf), from, fmt, args)
256 static int
257 string_enum_compare(const char *str1, const char *str2, int len)
259         size_t i;
261 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
263         /* Diff-Header == DIFF_HEADER */
264         for (i = 0; i < len; i++) {
265                 if (toupper(str1[i]) == toupper(str2[i]))
266                         continue;
268                 if (string_enum_sep(str1[i]) &&
269                     string_enum_sep(str2[i]))
270                         continue;
272                 return str1[i] - str2[i];
273         }
275         return 0;
278 /* Shell quoting
279  *
280  * NOTE: The following is a slightly modified copy of the git project's shell
281  * quoting routines found in the quote.c file.
282  *
283  * Help to copy the thing properly quoted for the shell safety.  any single
284  * quote is replaced with '\'', any exclamation point is replaced with '\!',
285  * and the whole thing is enclosed in a
286  *
287  * E.g.
288  *  original     sq_quote     result
289  *  name     ==> name      ==> 'name'
290  *  a b      ==> a b       ==> 'a b'
291  *  a'b      ==> a'\''b    ==> 'a'\''b'
292  *  a!b      ==> a'\!'b    ==> 'a'\!'b'
293  */
295 static size_t
296 sq_quote(char buf[SIZEOF_STR], size_t bufsize, const char *src)
298         char c;
300 #define BUFPUT(x) do { if (bufsize < SIZEOF_STR) buf[bufsize++] = (x); } while (0)
302         BUFPUT('\'');
303         while ((c = *src++)) {
304                 if (c == '\'' || c == '!') {
305                         BUFPUT('\'');
306                         BUFPUT('\\');
307                         BUFPUT(c);
308                         BUFPUT('\'');
309                 } else {
310                         BUFPUT(c);
311                 }
312         }
313         BUFPUT('\'');
315         if (bufsize < SIZEOF_STR)
316                 buf[bufsize] = 0;
318         return bufsize;
322 /*
323  * User requests
324  */
326 #define REQ_INFO \
327         /* XXX: Keep the view request first and in sync with views[]. */ \
328         REQ_GROUP("View switching") \
329         REQ_(VIEW_MAIN,         "Show main view"), \
330         REQ_(VIEW_DIFF,         "Show diff view"), \
331         REQ_(VIEW_LOG,          "Show log view"), \
332         REQ_(VIEW_TREE,         "Show tree view"), \
333         REQ_(VIEW_BLOB,         "Show blob view"), \
334         REQ_(VIEW_BLAME,        "Show blame view"), \
335         REQ_(VIEW_HELP,         "Show help page"), \
336         REQ_(VIEW_PAGER,        "Show pager view"), \
337         REQ_(VIEW_STATUS,       "Show status view"), \
338         REQ_(VIEW_STAGE,        "Show stage view"), \
339         \
340         REQ_GROUP("View manipulation") \
341         REQ_(ENTER,             "Enter current line and scroll"), \
342         REQ_(NEXT,              "Move to next"), \
343         REQ_(PREVIOUS,          "Move to previous"), \
344         REQ_(VIEW_NEXT,         "Move focus to next view"), \
345         REQ_(REFRESH,           "Reload and refresh"), \
346         REQ_(MAXIMIZE,          "Maximize the current view"), \
347         REQ_(VIEW_CLOSE,        "Close the current view"), \
348         REQ_(QUIT,              "Close all views and quit"), \
349         \
350         REQ_GROUP("Cursor navigation") \
351         REQ_(MOVE_UP,           "Move cursor one line up"), \
352         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
353         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
354         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
355         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
356         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
357         \
358         REQ_GROUP("Scrolling") \
359         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
360         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
361         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
362         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
363         \
364         REQ_GROUP("Searching") \
365         REQ_(SEARCH,            "Search the view"), \
366         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
367         REQ_(FIND_NEXT,         "Find next search match"), \
368         REQ_(FIND_PREV,         "Find previous search match"), \
369         \
370         REQ_GROUP("Misc") \
371         REQ_(PROMPT,            "Bring up the prompt"), \
372         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
373         REQ_(SCREEN_RESIZE,     "Resize the screen"), \
374         REQ_(SHOW_VERSION,      "Show version information"), \
375         REQ_(STOP_LOADING,      "Stop all loading views"), \
376         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
377         REQ_(TOGGLE_DATE,       "Toggle date display"), \
378         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
379         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
380         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
381         REQ_(STATUS_UPDATE,     "Update file status"), \
382         REQ_(STATUS_CHECKOUT,   "Checkout file"), \
383         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
384         REQ_(STAGE_NEXT,        "Find next chunk to stage"), \
385         REQ_(TREE_PARENT,       "Switch to parent directory in tree view"), \
386         REQ_(EDIT,              "Open in editor"), \
387         REQ_(NONE,              "Do nothing")
390 /* User action requests. */
391 enum request {
392 #define REQ_GROUP(help)
393 #define REQ_(req, help) REQ_##req
395         /* Offset all requests to avoid conflicts with ncurses getch values. */
396         REQ_OFFSET = KEY_MAX + 1,
397         REQ_INFO
399 #undef  REQ_GROUP
400 #undef  REQ_
401 };
403 struct request_info {
404         enum request request;
405         char *name;
406         int namelen;
407         char *help;
408 };
410 static struct request_info req_info[] = {
411 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
412 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
413         REQ_INFO
414 #undef  REQ_GROUP
415 #undef  REQ_
416 };
418 static enum request
419 get_request(const char *name)
421         int namelen = strlen(name);
422         int i;
424         for (i = 0; i < ARRAY_SIZE(req_info); i++)
425                 if (req_info[i].namelen == namelen &&
426                     !string_enum_compare(req_info[i].name, name, namelen))
427                         return req_info[i].request;
429         return REQ_NONE;
433 /*
434  * Options
435  */
437 static const char usage[] =
438 "tig " TIG_VERSION " (" __DATE__ ")\n"
439 "\n"
440 "Usage: tig        [options] [revs] [--] [paths]\n"
441 "   or: tig show   [options] [revs] [--] [paths]\n"
442 "   or: tig blame  [rev] path\n"
443 "   or: tig status\n"
444 "   or: tig <      [git command output]\n"
445 "\n"
446 "Options:\n"
447 "  -v, --version   Show version and exit\n"
448 "  -h, --help      Show help message and exit";
450 /* Option and state variables. */
451 static bool opt_date                    = TRUE;
452 static bool opt_author                  = TRUE;
453 static bool opt_line_number             = FALSE;
454 static bool opt_line_graphics           = TRUE;
455 static bool opt_rev_graph               = FALSE;
456 static bool opt_show_refs               = TRUE;
457 static int opt_num_interval             = NUMBER_INTERVAL;
458 static int opt_tab_size                 = TAB_SIZE;
459 static int opt_author_cols              = AUTHOR_COLS-1;
460 static char opt_cmd[SIZEOF_STR]         = "";
461 static char opt_path[SIZEOF_STR]        = "";
462 static char opt_file[SIZEOF_STR]        = "";
463 static char opt_ref[SIZEOF_REF]         = "";
464 static char opt_head[SIZEOF_REF]        = "";
465 static char opt_remote[SIZEOF_REF]      = "";
466 static bool opt_no_head                 = TRUE;
467 static FILE *opt_pipe                   = NULL;
468 static char opt_encoding[20]            = "UTF-8";
469 static bool opt_utf8                    = TRUE;
470 static char opt_codeset[20]             = "UTF-8";
471 static iconv_t opt_iconv                = ICONV_NONE;
472 static char opt_search[SIZEOF_STR]      = "";
473 static char opt_cdup[SIZEOF_STR]        = "";
474 static char opt_git_dir[SIZEOF_STR]     = "";
475 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
476 static char opt_editor[SIZEOF_STR]      = "";
478 static enum request
479 parse_options(int argc, char *argv[])
481         enum request request = REQ_VIEW_MAIN;
482         size_t buf_size;
483         char *subcommand;
484         bool seen_dashdash = FALSE;
485         int i;
487         if (!isatty(STDIN_FILENO)) {
488                 opt_pipe = stdin;
489                 return REQ_VIEW_PAGER;
490         }
492         if (argc <= 1)
493                 return REQ_VIEW_MAIN;
495         subcommand = argv[1];
496         if (!strcmp(subcommand, "status") || !strcmp(subcommand, "-S")) {
497                 if (!strcmp(subcommand, "-S"))
498                         warn("`-S' has been deprecated; use `tig status' instead");
499                 if (argc > 2)
500                         warn("ignoring arguments after `%s'", subcommand);
501                 return REQ_VIEW_STATUS;
503         } else if (!strcmp(subcommand, "blame")) {
504                 if (argc <= 2 || argc > 4)
505                         die("invalid number of options to blame\n\n%s", usage);
507                 i = 2;
508                 if (argc == 4) {
509                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
510                         i++;
511                 }
513                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
514                 return REQ_VIEW_BLAME;
516         } else if (!strcmp(subcommand, "show")) {
517                 request = REQ_VIEW_DIFF;
519         } else if (!strcmp(subcommand, "log") || !strcmp(subcommand, "diff")) {
520                 request = subcommand[0] == 'l' ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
521                 warn("`tig %s' has been deprecated", subcommand);
523         } else {
524                 subcommand = NULL;
525         }
527         if (!subcommand)
528                 /* XXX: This is vulnerable to the user overriding
529                  * options required for the main view parser. */
530                 string_copy(opt_cmd, TIG_MAIN_BASE);
531         else
532                 string_format(opt_cmd, "git %s", subcommand);
534         buf_size = strlen(opt_cmd);
536         for (i = 1 + !!subcommand; i < argc; i++) {
537                 char *opt = argv[i];
539                 if (seen_dashdash || !strcmp(opt, "--")) {
540                         seen_dashdash = TRUE;
542                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
543                         printf("tig version %s\n", TIG_VERSION);
544                         return REQ_NONE;
546                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
547                         printf("%s\n", usage);
548                         return REQ_NONE;
549                 }
551                 opt_cmd[buf_size++] = ' ';
552                 buf_size = sq_quote(opt_cmd, buf_size, opt);
553                 if (buf_size >= sizeof(opt_cmd))
554                         die("command too long");
555         }
557         opt_cmd[buf_size] = 0;
559         return request;
563 /*
564  * Line-oriented content detection.
565  */
567 #define LINE_INFO \
568 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
569 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
570 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
571 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
572 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
573 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
574 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
575 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
576 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
577 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
578 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
579 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
580 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
581 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
582 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
583 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
584 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
585 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
586 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
587 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
588 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
589 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
590 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
591 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
592 LINE(AUTHOR,       "author ",           COLOR_CYAN,     COLOR_DEFAULT,  0), \
593 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
594 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
595 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
596 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
597 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
598 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
599 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
600 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
601 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
602 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
603 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
604 LINE(MAIN_AUTHOR,  "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
605 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
606 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
607 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
608 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
609 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
610 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
611 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
612 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
613 LINE(TREE_DIR,     "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
614 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
615 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
616 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
617 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
618 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
619 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
620 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
621 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
623 enum line_type {
624 #define LINE(type, line, fg, bg, attr) \
625         LINE_##type
626         LINE_INFO,
627         LINE_NONE
628 #undef  LINE
629 };
631 struct line_info {
632         const char *name;       /* Option name. */
633         int namelen;            /* Size of option name. */
634         const char *line;       /* The start of line to match. */
635         int linelen;            /* Size of string to match. */
636         int fg, bg, attr;       /* Color and text attributes for the lines. */
637 };
639 static struct line_info line_info[] = {
640 #define LINE(type, line, fg, bg, attr) \
641         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
642         LINE_INFO
643 #undef  LINE
644 };
646 static enum line_type
647 get_line_type(char *line)
649         int linelen = strlen(line);
650         enum line_type type;
652         for (type = 0; type < ARRAY_SIZE(line_info); type++)
653                 /* Case insensitive search matches Signed-off-by lines better. */
654                 if (linelen >= line_info[type].linelen &&
655                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
656                         return type;
658         return LINE_DEFAULT;
661 static inline int
662 get_line_attr(enum line_type type)
664         assert(type < ARRAY_SIZE(line_info));
665         return COLOR_PAIR(type) | line_info[type].attr;
668 static struct line_info *
669 get_line_info(char *name)
671         size_t namelen = strlen(name);
672         enum line_type type;
674         for (type = 0; type < ARRAY_SIZE(line_info); type++)
675                 if (namelen == line_info[type].namelen &&
676                     !string_enum_compare(line_info[type].name, name, namelen))
677                         return &line_info[type];
679         return NULL;
682 static void
683 init_colors(void)
685         int default_bg = line_info[LINE_DEFAULT].bg;
686         int default_fg = line_info[LINE_DEFAULT].fg;
687         enum line_type type;
689         start_color();
691         if (assume_default_colors(default_fg, default_bg) == ERR) {
692                 default_bg = COLOR_BLACK;
693                 default_fg = COLOR_WHITE;
694         }
696         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
697                 struct line_info *info = &line_info[type];
698                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
699                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
701                 init_pair(type, fg, bg);
702         }
705 struct line {
706         enum line_type type;
708         /* State flags */
709         unsigned int selected:1;
710         unsigned int dirty:1;
712         void *data;             /* User data */
713 };
716 /*
717  * Keys
718  */
720 struct keybinding {
721         int alias;
722         enum request request;
723         struct keybinding *next;
724 };
726 static struct keybinding default_keybindings[] = {
727         /* View switching */
728         { 'm',          REQ_VIEW_MAIN },
729         { 'd',          REQ_VIEW_DIFF },
730         { 'l',          REQ_VIEW_LOG },
731         { 't',          REQ_VIEW_TREE },
732         { 'f',          REQ_VIEW_BLOB },
733         { 'B',          REQ_VIEW_BLAME },
734         { 'p',          REQ_VIEW_PAGER },
735         { 'h',          REQ_VIEW_HELP },
736         { 'S',          REQ_VIEW_STATUS },
737         { 'c',          REQ_VIEW_STAGE },
739         /* View manipulation */
740         { 'q',          REQ_VIEW_CLOSE },
741         { KEY_TAB,      REQ_VIEW_NEXT },
742         { KEY_RETURN,   REQ_ENTER },
743         { KEY_UP,       REQ_PREVIOUS },
744         { KEY_DOWN,     REQ_NEXT },
745         { 'R',          REQ_REFRESH },
746         { KEY_F(5),     REQ_REFRESH },
747         { 'O',          REQ_MAXIMIZE },
749         /* Cursor navigation */
750         { 'k',          REQ_MOVE_UP },
751         { 'j',          REQ_MOVE_DOWN },
752         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
753         { KEY_END,      REQ_MOVE_LAST_LINE },
754         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
755         { ' ',          REQ_MOVE_PAGE_DOWN },
756         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
757         { 'b',          REQ_MOVE_PAGE_UP },
758         { '-',          REQ_MOVE_PAGE_UP },
760         /* Scrolling */
761         { KEY_IC,       REQ_SCROLL_LINE_UP },
762         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
763         { 'w',          REQ_SCROLL_PAGE_UP },
764         { 's',          REQ_SCROLL_PAGE_DOWN },
766         /* Searching */
767         { '/',          REQ_SEARCH },
768         { '?',          REQ_SEARCH_BACK },
769         { 'n',          REQ_FIND_NEXT },
770         { 'N',          REQ_FIND_PREV },
772         /* Misc */
773         { 'Q',          REQ_QUIT },
774         { 'z',          REQ_STOP_LOADING },
775         { 'v',          REQ_SHOW_VERSION },
776         { 'r',          REQ_SCREEN_REDRAW },
777         { '.',          REQ_TOGGLE_LINENO },
778         { 'D',          REQ_TOGGLE_DATE },
779         { 'A',          REQ_TOGGLE_AUTHOR },
780         { 'g',          REQ_TOGGLE_REV_GRAPH },
781         { 'F',          REQ_TOGGLE_REFS },
782         { ':',          REQ_PROMPT },
783         { 'u',          REQ_STATUS_UPDATE },
784         { '!',          REQ_STATUS_CHECKOUT },
785         { 'M',          REQ_STATUS_MERGE },
786         { '@',          REQ_STAGE_NEXT },
787         { ',',          REQ_TREE_PARENT },
788         { 'e',          REQ_EDIT },
790         /* Using the ncurses SIGWINCH handler. */
791         { KEY_RESIZE,   REQ_SCREEN_RESIZE },
792 };
794 #define KEYMAP_INFO \
795         KEYMAP_(GENERIC), \
796         KEYMAP_(MAIN), \
797         KEYMAP_(DIFF), \
798         KEYMAP_(LOG), \
799         KEYMAP_(TREE), \
800         KEYMAP_(BLOB), \
801         KEYMAP_(BLAME), \
802         KEYMAP_(PAGER), \
803         KEYMAP_(HELP), \
804         KEYMAP_(STATUS), \
805         KEYMAP_(STAGE)
807 enum keymap {
808 #define KEYMAP_(name) KEYMAP_##name
809         KEYMAP_INFO
810 #undef  KEYMAP_
811 };
813 static struct int_map keymap_table[] = {
814 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
815         KEYMAP_INFO
816 #undef  KEYMAP_
817 };
819 #define set_keymap(map, name) \
820         set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
822 static struct keybinding *keybindings[ARRAY_SIZE(keymap_table)];
824 static void
825 add_keybinding(enum keymap keymap, enum request request, int key)
827         struct keybinding *keybinding;
829         keybinding = calloc(1, sizeof(*keybinding));
830         if (!keybinding)
831                 die("Failed to allocate keybinding");
833         keybinding->alias = key;
834         keybinding->request = request;
835         keybinding->next = keybindings[keymap];
836         keybindings[keymap] = keybinding;
839 /* Looks for a key binding first in the given map, then in the generic map, and
840  * lastly in the default keybindings. */
841 static enum request
842 get_keybinding(enum keymap keymap, int key)
844         struct keybinding *kbd;
845         int i;
847         for (kbd = keybindings[keymap]; kbd; kbd = kbd->next)
848                 if (kbd->alias == key)
849                         return kbd->request;
851         for (kbd = keybindings[KEYMAP_GENERIC]; kbd; kbd = kbd->next)
852                 if (kbd->alias == key)
853                         return kbd->request;
855         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
856                 if (default_keybindings[i].alias == key)
857                         return default_keybindings[i].request;
859         return (enum request) key;
863 struct key {
864         char *name;
865         int value;
866 };
868 static struct key key_table[] = {
869         { "Enter",      KEY_RETURN },
870         { "Space",      ' ' },
871         { "Backspace",  KEY_BACKSPACE },
872         { "Tab",        KEY_TAB },
873         { "Escape",     KEY_ESC },
874         { "Left",       KEY_LEFT },
875         { "Right",      KEY_RIGHT },
876         { "Up",         KEY_UP },
877         { "Down",       KEY_DOWN },
878         { "Insert",     KEY_IC },
879         { "Delete",     KEY_DC },
880         { "Hash",       '#' },
881         { "Home",       KEY_HOME },
882         { "End",        KEY_END },
883         { "PageUp",     KEY_PPAGE },
884         { "PageDown",   KEY_NPAGE },
885         { "F1",         KEY_F(1) },
886         { "F2",         KEY_F(2) },
887         { "F3",         KEY_F(3) },
888         { "F4",         KEY_F(4) },
889         { "F5",         KEY_F(5) },
890         { "F6",         KEY_F(6) },
891         { "F7",         KEY_F(7) },
892         { "F8",         KEY_F(8) },
893         { "F9",         KEY_F(9) },
894         { "F10",        KEY_F(10) },
895         { "F11",        KEY_F(11) },
896         { "F12",        KEY_F(12) },
897 };
899 static int
900 get_key_value(const char *name)
902         int i;
904         for (i = 0; i < ARRAY_SIZE(key_table); i++)
905                 if (!strcasecmp(key_table[i].name, name))
906                         return key_table[i].value;
908         if (strlen(name) == 1 && isprint(*name))
909                 return (int) *name;
911         return ERR;
914 static char *
915 get_key_name(int key_value)
917         static char key_char[] = "'X'";
918         char *seq = NULL;
919         int key;
921         for (key = 0; key < ARRAY_SIZE(key_table); key++)
922                 if (key_table[key].value == key_value)
923                         seq = key_table[key].name;
925         if (seq == NULL &&
926             key_value < 127 &&
927             isprint(key_value)) {
928                 key_char[1] = (char) key_value;
929                 seq = key_char;
930         }
932         return seq ? seq : "'?'";
935 static char *
936 get_key(enum request request)
938         static char buf[BUFSIZ];
939         size_t pos = 0;
940         char *sep = "";
941         int i;
943         buf[pos] = 0;
945         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
946                 struct keybinding *keybinding = &default_keybindings[i];
948                 if (keybinding->request != request)
949                         continue;
951                 if (!string_format_from(buf, &pos, "%s%s", sep,
952                                         get_key_name(keybinding->alias)))
953                         return "Too many keybindings!";
954                 sep = ", ";
955         }
957         return buf;
960 struct run_request {
961         enum keymap keymap;
962         int key;
963         char cmd[SIZEOF_STR];
964 };
966 static struct run_request *run_request;
967 static size_t run_requests;
969 static enum request
970 add_run_request(enum keymap keymap, int key, int argc, char **argv)
972         struct run_request *req;
973         char cmd[SIZEOF_STR];
974         size_t bufpos;
976         for (bufpos = 0; argc > 0; argc--, argv++)
977                 if (!string_format_from(cmd, &bufpos, "%s ", *argv))
978                         return REQ_NONE;
980         req = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
981         if (!req)
982                 return REQ_NONE;
984         run_request = req;
985         req = &run_request[run_requests++];
986         string_copy(req->cmd, cmd);
987         req->keymap = keymap;
988         req->key = key;
990         return REQ_NONE + run_requests;
993 static struct run_request *
994 get_run_request(enum request request)
996         if (request <= REQ_NONE)
997                 return NULL;
998         return &run_request[request - REQ_NONE - 1];
1001 static void
1002 add_builtin_run_requests(void)
1004         struct {
1005                 enum keymap keymap;
1006                 int key;
1007                 char *argv[1];
1008         } reqs[] = {
1009                 { KEYMAP_MAIN,    'C', { "git cherry-pick %(commit)" } },
1010                 { KEYMAP_GENERIC, 'G', { "git gc" } },
1011         };
1012         int i;
1014         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1015                 enum request req;
1017                 req = add_run_request(reqs[i].keymap, reqs[i].key, 1, reqs[i].argv);
1018                 if (req != REQ_NONE)
1019                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1020         }
1023 /*
1024  * User config file handling.
1025  */
1027 static struct int_map color_map[] = {
1028 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
1029         COLOR_MAP(DEFAULT),
1030         COLOR_MAP(BLACK),
1031         COLOR_MAP(BLUE),
1032         COLOR_MAP(CYAN),
1033         COLOR_MAP(GREEN),
1034         COLOR_MAP(MAGENTA),
1035         COLOR_MAP(RED),
1036         COLOR_MAP(WHITE),
1037         COLOR_MAP(YELLOW),
1038 };
1040 #define set_color(color, name) \
1041         set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
1043 static struct int_map attr_map[] = {
1044 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
1045         ATTR_MAP(NORMAL),
1046         ATTR_MAP(BLINK),
1047         ATTR_MAP(BOLD),
1048         ATTR_MAP(DIM),
1049         ATTR_MAP(REVERSE),
1050         ATTR_MAP(STANDOUT),
1051         ATTR_MAP(UNDERLINE),
1052 };
1054 #define set_attribute(attr, name) \
1055         set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
1057 static int   config_lineno;
1058 static bool  config_errors;
1059 static char *config_msg;
1061 /* Wants: object fgcolor bgcolor [attr] */
1062 static int
1063 option_color_command(int argc, char *argv[])
1065         struct line_info *info;
1067         if (argc != 3 && argc != 4) {
1068                 config_msg = "Wrong number of arguments given to color command";
1069                 return ERR;
1070         }
1072         info = get_line_info(argv[0]);
1073         if (!info) {
1074                 if (!string_enum_compare(argv[0], "main-delim", strlen("main-delim"))) {
1075                         info = get_line_info("delimiter");
1077                 } else if (!string_enum_compare(argv[0], "main-date", strlen("main-date"))) {
1078                         info = get_line_info("date");
1080                 } else {
1081                         config_msg = "Unknown color name";
1082                         return ERR;
1083                 }
1084         }
1086         if (set_color(&info->fg, argv[1]) == ERR ||
1087             set_color(&info->bg, argv[2]) == ERR) {
1088                 config_msg = "Unknown color";
1089                 return ERR;
1090         }
1092         if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1093                 config_msg = "Unknown attribute";
1094                 return ERR;
1095         }
1097         return OK;
1100 static bool parse_bool(const char *s)
1102         return (!strcmp(s, "1") || !strcmp(s, "true") ||
1103                 !strcmp(s, "yes")) ? TRUE : FALSE;
1106 static int
1107 parse_int(const char *s, int default_value, int min, int max)
1109         int value = atoi(s);
1111         return (value < min || value > max) ? default_value : value;
1114 /* Wants: name = value */
1115 static int
1116 option_set_command(int argc, char *argv[])
1118         if (argc != 3) {
1119                 config_msg = "Wrong number of arguments given to set command";
1120                 return ERR;
1121         }
1123         if (strcmp(argv[1], "=")) {
1124                 config_msg = "No value assigned";
1125                 return ERR;
1126         }
1128         if (!strcmp(argv[0], "show-author")) {
1129                 opt_author = parse_bool(argv[2]);
1130                 return OK;
1131         }
1133         if (!strcmp(argv[0], "show-date")) {
1134                 opt_date = parse_bool(argv[2]);
1135                 return OK;
1136         }
1138         if (!strcmp(argv[0], "show-rev-graph")) {
1139                 opt_rev_graph = parse_bool(argv[2]);
1140                 return OK;
1141         }
1143         if (!strcmp(argv[0], "show-refs")) {
1144                 opt_show_refs = parse_bool(argv[2]);
1145                 return OK;
1146         }
1148         if (!strcmp(argv[0], "show-line-numbers")) {
1149                 opt_line_number = parse_bool(argv[2]);
1150                 return OK;
1151         }
1153         if (!strcmp(argv[0], "line-graphics")) {
1154                 opt_line_graphics = parse_bool(argv[2]);
1155                 return OK;
1156         }
1158         if (!strcmp(argv[0], "line-number-interval")) {
1159                 opt_num_interval = parse_int(argv[2], opt_num_interval, 1, 1024);
1160                 return OK;
1161         }
1163         if (!strcmp(argv[0], "author-width")) {
1164                 opt_author_cols = parse_int(argv[2], opt_author_cols, 0, 1024);
1165                 return OK;
1166         }
1168         if (!strcmp(argv[0], "tab-size")) {
1169                 opt_tab_size = parse_int(argv[2], opt_tab_size, 1, 1024);
1170                 return OK;
1171         }
1173         if (!strcmp(argv[0], "commit-encoding")) {
1174                 char *arg = argv[2];
1175                 int delimiter = *arg;
1176                 int i;
1178                 switch (delimiter) {
1179                 case '"':
1180                 case '\'':
1181                         for (arg++, i = 0; arg[i]; i++)
1182                                 if (arg[i] == delimiter) {
1183                                         arg[i] = 0;
1184                                         break;
1185                                 }
1186                 default:
1187                         string_ncopy(opt_encoding, arg, strlen(arg));
1188                         return OK;
1189                 }
1190         }
1192         config_msg = "Unknown variable name";
1193         return ERR;
1196 /* Wants: mode request key */
1197 static int
1198 option_bind_command(int argc, char *argv[])
1200         enum request request;
1201         int keymap;
1202         int key;
1204         if (argc < 3) {
1205                 config_msg = "Wrong number of arguments given to bind command";
1206                 return ERR;
1207         }
1209         if (set_keymap(&keymap, argv[0]) == ERR) {
1210                 config_msg = "Unknown key map";
1211                 return ERR;
1212         }
1214         key = get_key_value(argv[1]);
1215         if (key == ERR) {
1216                 config_msg = "Unknown key";
1217                 return ERR;
1218         }
1220         request = get_request(argv[2]);
1221         if (request == REQ_NONE) {
1222                 const char *obsolete[] = { "cherry-pick" };
1223                 size_t namelen = strlen(argv[2]);
1224                 int i;
1226                 for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
1227                         if (namelen == strlen(obsolete[i]) &&
1228                             !string_enum_compare(obsolete[i], argv[2], namelen)) {
1229                                 config_msg = "Obsolete request name";
1230                                 return ERR;
1231                         }
1232                 }
1233         }
1234         if (request == REQ_NONE && *argv[2]++ == '!')
1235                 request = add_run_request(keymap, key, argc - 2, argv + 2);
1236         if (request == REQ_NONE) {
1237                 config_msg = "Unknown request name";
1238                 return ERR;
1239         }
1241         add_keybinding(keymap, request, key);
1243         return OK;
1246 static int
1247 set_option(char *opt, char *value)
1249         char *argv[16];
1250         int valuelen;
1251         int argc = 0;
1253         /* Tokenize */
1254         while (argc < ARRAY_SIZE(argv) && (valuelen = strcspn(value, " \t"))) {
1255                 argv[argc++] = value;
1256                 value += valuelen;
1258                 /* Nothing more to tokenize or last available token. */
1259                 if (!*value || argc >= ARRAY_SIZE(argv))
1260                         break;
1262                 *value++ = 0;
1263                 while (isspace(*value))
1264                         value++;
1265         }
1267         if (!strcmp(opt, "color"))
1268                 return option_color_command(argc, argv);
1270         if (!strcmp(opt, "set"))
1271                 return option_set_command(argc, argv);
1273         if (!strcmp(opt, "bind"))
1274                 return option_bind_command(argc, argv);
1276         config_msg = "Unknown option command";
1277         return ERR;
1280 static int
1281 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1283         int status = OK;
1285         config_lineno++;
1286         config_msg = "Internal error";
1288         /* Check for comment markers, since read_properties() will
1289          * only ensure opt and value are split at first " \t". */
1290         optlen = strcspn(opt, "#");
1291         if (optlen == 0)
1292                 return OK;
1294         if (opt[optlen] != 0) {
1295                 config_msg = "No option value";
1296                 status = ERR;
1298         }  else {
1299                 /* Look for comment endings in the value. */
1300                 size_t len = strcspn(value, "#");
1302                 if (len < valuelen) {
1303                         valuelen = len;
1304                         value[valuelen] = 0;
1305                 }
1307                 status = set_option(opt, value);
1308         }
1310         if (status == ERR) {
1311                 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1312                         config_lineno, (int) optlen, opt, config_msg);
1313                 config_errors = TRUE;
1314         }
1316         /* Always keep going if errors are encountered. */
1317         return OK;
1320 static void
1321 load_option_file(const char *path)
1323         FILE *file;
1325         /* It's ok that the file doesn't exist. */
1326         file = fopen(path, "r");
1327         if (!file)
1328                 return;
1330         config_lineno = 0;
1331         config_errors = FALSE;
1333         if (read_properties(file, " \t", read_option) == ERR ||
1334             config_errors == TRUE)
1335                 fprintf(stderr, "Errors while loading %s.\n", path);
1338 static int
1339 load_options(void)
1341         char *home = getenv("HOME");
1342         char *tigrc_user = getenv("TIGRC_USER");
1343         char *tigrc_system = getenv("TIGRC_SYSTEM");
1344         char buf[SIZEOF_STR];
1346         add_builtin_run_requests();
1348         if (!tigrc_system) {
1349                 if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
1350                         return ERR;
1351                 tigrc_system = buf;
1352         }
1353         load_option_file(tigrc_system);
1355         if (!tigrc_user) {
1356                 if (!home || !string_format(buf, "%s/.tigrc", home))
1357                         return ERR;
1358                 tigrc_user = buf;
1359         }
1360         load_option_file(tigrc_user);
1362         return OK;
1366 /*
1367  * The viewer
1368  */
1370 struct view;
1371 struct view_ops;
1373 /* The display array of active views and the index of the current view. */
1374 static struct view *display[2];
1375 static unsigned int current_view;
1377 /* Reading from the prompt? */
1378 static bool input_mode = FALSE;
1380 #define foreach_displayed_view(view, i) \
1381         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1383 #define displayed_views()       (display[1] != NULL ? 2 : 1)
1385 /* Current head and commit ID */
1386 static char ref_blob[SIZEOF_REF]        = "";
1387 static char ref_commit[SIZEOF_REF]      = "HEAD";
1388 static char ref_head[SIZEOF_REF]        = "HEAD";
1390 struct view {
1391         const char *name;       /* View name */
1392         const char *cmd_fmt;    /* Default command line format */
1393         const char *cmd_env;    /* Command line set via environment */
1394         const char *id;         /* Points to either of ref_{head,commit,blob} */
1396         struct view_ops *ops;   /* View operations */
1398         enum keymap keymap;     /* What keymap does this view have */
1399         bool git_dir;           /* Whether the view requires a git directory. */
1401         char cmd[SIZEOF_STR];   /* Command buffer */
1402         char ref[SIZEOF_REF];   /* Hovered commit reference */
1403         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
1405         int height, width;      /* The width and height of the main window */
1406         WINDOW *win;            /* The main window */
1407         WINDOW *title;          /* The title window living below the main window */
1409         /* Navigation */
1410         unsigned long offset;   /* Offset of the window top */
1411         unsigned long lineno;   /* Current line number */
1413         /* Searching */
1414         char grep[SIZEOF_STR];  /* Search string */
1415         regex_t *regex;         /* Pre-compiled regex */
1417         /* If non-NULL, points to the view that opened this view. If this view
1418          * is closed tig will switch back to the parent view. */
1419         struct view *parent;
1421         /* Buffering */
1422         size_t lines;           /* Total number of lines */
1423         struct line *line;      /* Line index */
1424         size_t line_alloc;      /* Total number of allocated lines */
1425         size_t line_size;       /* Total number of used lines */
1426         unsigned int digits;    /* Number of digits in the lines member. */
1428         /* Drawing */
1429         struct line *curline;   /* Line currently being drawn. */
1430         enum line_type curtype; /* Attribute currently used for drawing. */
1431         unsigned long col;      /* Column when drawing. */
1433         /* Loading */
1434         FILE *pipe;
1435         time_t start_time;
1436 };
1438 struct view_ops {
1439         /* What type of content being displayed. Used in the title bar. */
1440         const char *type;
1441         /* Open and reads in all view content. */
1442         bool (*open)(struct view *view);
1443         /* Read one line; updates view->line. */
1444         bool (*read)(struct view *view, char *data);
1445         /* Draw one line; @lineno must be < view->height. */
1446         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1447         /* Depending on view handle a special requests. */
1448         enum request (*request)(struct view *view, enum request request, struct line *line);
1449         /* Search for regex in a line. */
1450         bool (*grep)(struct view *view, struct line *line);
1451         /* Select line */
1452         void (*select)(struct view *view, struct line *line);
1453 };
1455 static struct view_ops pager_ops;
1456 static struct view_ops main_ops;
1457 static struct view_ops tree_ops;
1458 static struct view_ops blob_ops;
1459 static struct view_ops blame_ops;
1460 static struct view_ops help_ops;
1461 static struct view_ops status_ops;
1462 static struct view_ops stage_ops;
1464 #define VIEW_STR(name, cmd, env, ref, ops, map, git) \
1465         { name, cmd, #env, ref, ops, map, git }
1467 #define VIEW_(id, name, ops, git, ref) \
1468         VIEW_STR(name, TIG_##id##_CMD,  TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1471 static struct view views[] = {
1472         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
1473         VIEW_(DIFF,   "diff",   &pager_ops,  TRUE,  ref_commit),
1474         VIEW_(LOG,    "log",    &pager_ops,  TRUE,  ref_head),
1475         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
1476         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
1477         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
1478         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
1479         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, "stdin"),
1480         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
1481         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
1482 };
1484 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
1485 #define VIEW_REQ(view)  ((view) - views + REQ_OFFSET + 1)
1487 #define foreach_view(view, i) \
1488         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1490 #define view_is_displayed(view) \
1491         (view == display[0] || view == display[1])
1494 enum line_graphic {
1495         LINE_GRAPHIC_VLINE
1496 };
1498 static int line_graphics[] = {
1499         /* LINE_GRAPHIC_VLINE: */ '|'
1500 };
1502 static inline void
1503 set_view_attr(struct view *view, enum line_type type)
1505         if (!view->curline->selected && view->curtype != type) {
1506                 wattrset(view->win, get_line_attr(type));
1507                 wchgat(view->win, -1, 0, type, NULL);
1508                 view->curtype = type;
1509         }
1512 static int
1513 draw_chars(struct view *view, enum line_type type, const char *string,
1514            int max_len, bool use_tilde)
1516         int len = 0;
1517         int col = 0;
1518         int trimmed = FALSE;
1520         if (max_len <= 0)
1521                 return 0;
1523         if (opt_utf8) {
1524                 len = utf8_length(string, &col, max_len, &trimmed, use_tilde);
1525         } else {
1526                 col = len = strlen(string);
1527                 if (len > max_len) {
1528                         if (use_tilde) {
1529                                 max_len -= 1;
1530                         }
1531                         col = len = max_len;
1532                         trimmed = TRUE;
1533                 }
1534         }
1536         set_view_attr(view, type);
1537         waddnstr(view->win, string, len);
1538         if (trimmed && use_tilde) {
1539                 set_view_attr(view, LINE_DELIMITER);
1540                 waddch(view->win, '~');
1541                 col++;
1542         }
1544         return col;
1547 static int
1548 draw_space(struct view *view, enum line_type type, int max, int spaces)
1550         static char space[] = "                    ";
1551         int col = 0;
1553         spaces = MIN(max, spaces);
1555         while (spaces > 0) {
1556                 int len = MIN(spaces, sizeof(space) - 1);
1558                 col += draw_chars(view, type, space, spaces, FALSE);
1559                 spaces -= len;
1560         }
1562         return col;
1565 static bool
1566 draw_lineno(struct view *view, unsigned int lineno)
1568         char number[10];
1569         int digits3 = view->digits < 3 ? 3 : view->digits;
1570         int max_number = MIN(digits3, STRING_SIZE(number));
1571         int max = view->width - view->col;
1572         int col;
1574         if (max < max_number)
1575                 max_number = max;
1577         lineno += view->offset + 1;
1578         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
1579                 static char fmt[] = "%1ld";
1581                 if (view->digits <= 9)
1582                         fmt[1] = '0' + digits3;
1584                 if (!string_format(number, fmt, lineno))
1585                         number[0] = 0;
1586                 col = draw_chars(view, LINE_LINE_NUMBER, number, max_number, TRUE);
1587         } else {
1588                 col = draw_space(view, LINE_LINE_NUMBER, max_number, max_number);
1589         }
1591         if (col < max) {
1592                 set_view_attr(view, LINE_DEFAULT);
1593                 waddch(view->win, line_graphics[LINE_GRAPHIC_VLINE]);
1594                 col++;
1595         }
1597         if (col < max)
1598                 col += draw_space(view, LINE_DEFAULT, max - col, 1);
1599         view->col += col;
1601         return view->width - view->col <= 0;
1604 static bool
1605 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1607         view->col += draw_chars(view, type, string, view->width - view->col, trim);
1608         return view->width - view->col <= 0;
1611 static bool
1612 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1614         int max = view->width - view->col;
1615         int i;
1617         if (max < size)
1618                 size = max;
1620         set_view_attr(view, type);
1621         /* Using waddch() instead of waddnstr() ensures that
1622          * they'll be rendered correctly for the cursor line. */
1623         for (i = 0; i < size; i++)
1624                 waddch(view->win, graphic[i]);
1626         view->col += size;
1627         if (size < max) {
1628                 waddch(view->win, ' ');
1629                 view->col++;
1630         }
1632         return view->width - view->col <= 0;
1635 static bool
1636 draw_field(struct view *view, enum line_type type, char *text, int len, bool trim)
1638         int max = MIN(view->width - view->col, len);
1639         int col;
1641         if (text)
1642                 col = draw_chars(view, type, text, max - 1, trim);
1643         else
1644                 col = draw_space(view, type, max - 1, max - 1);
1646         view->col += col + draw_space(view, LINE_DEFAULT, max - col, max - col);
1647         return view->width - view->col <= 0;
1650 static bool
1651 draw_date(struct view *view, struct tm *time)
1653         char buf[DATE_COLS];
1654         char *date;
1655         int timelen = 0;
1657         if (time)
1658                 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
1659         date = timelen ? buf : NULL;
1661         return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
1664 static bool
1665 draw_view_line(struct view *view, unsigned int lineno)
1667         struct line *line;
1668         bool selected = (view->offset + lineno == view->lineno);
1669         bool draw_ok;
1671         assert(view_is_displayed(view));
1673         if (view->offset + lineno >= view->lines)
1674                 return FALSE;
1676         line = &view->line[view->offset + lineno];
1678         wmove(view->win, lineno, 0);
1679         view->col = 0;
1680         view->curline = line;
1681         view->curtype = LINE_NONE;
1682         line->selected = FALSE;
1684         if (selected) {
1685                 set_view_attr(view, LINE_CURSOR);
1686                 line->selected = TRUE;
1687                 view->ops->select(view, line);
1688         } else if (line->selected) {
1689                 wclrtoeol(view->win);
1690         }
1692         scrollok(view->win, FALSE);
1693         draw_ok = view->ops->draw(view, line, lineno);
1694         scrollok(view->win, TRUE);
1696         return draw_ok;
1699 static void
1700 redraw_view_dirty(struct view *view)
1702         bool dirty = FALSE;
1703         int lineno;
1705         for (lineno = 0; lineno < view->height; lineno++) {
1706                 struct line *line = &view->line[view->offset + lineno];
1708                 if (!line->dirty)
1709                         continue;
1710                 line->dirty = 0;
1711                 dirty = TRUE;
1712                 if (!draw_view_line(view, lineno))
1713                         break;
1714         }
1716         if (!dirty)
1717                 return;
1718         redrawwin(view->win);
1719         if (input_mode)
1720                 wnoutrefresh(view->win);
1721         else
1722                 wrefresh(view->win);
1725 static void
1726 redraw_view_from(struct view *view, int lineno)
1728         assert(0 <= lineno && lineno < view->height);
1730         for (; lineno < view->height; lineno++) {
1731                 if (!draw_view_line(view, lineno))
1732                         break;
1733         }
1735         redrawwin(view->win);
1736         if (input_mode)
1737                 wnoutrefresh(view->win);
1738         else
1739                 wrefresh(view->win);
1742 static void
1743 redraw_view(struct view *view)
1745         wclear(view->win);
1746         redraw_view_from(view, 0);
1750 static void
1751 update_view_title(struct view *view)
1753         char buf[SIZEOF_STR];
1754         char state[SIZEOF_STR];
1755         size_t bufpos = 0, statelen = 0;
1757         assert(view_is_displayed(view));
1759         if (view != VIEW(REQ_VIEW_STATUS) && (view->lines || view->pipe)) {
1760                 unsigned int view_lines = view->offset + view->height;
1761                 unsigned int lines = view->lines
1762                                    ? MIN(view_lines, view->lines) * 100 / view->lines
1763                                    : 0;
1765                 string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
1766                                    view->ops->type,
1767                                    view->lineno + 1,
1768                                    view->lines,
1769                                    lines);
1771                 if (view->pipe) {
1772                         time_t secs = time(NULL) - view->start_time;
1774                         /* Three git seconds are a long time ... */
1775                         if (secs > 2)
1776                                 string_format_from(state, &statelen, " %lds", secs);
1777                 }
1778         }
1780         string_format_from(buf, &bufpos, "[%s]", view->name);
1781         if (*view->ref && bufpos < view->width) {
1782                 size_t refsize = strlen(view->ref);
1783                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
1785                 if (minsize < view->width)
1786                         refsize = view->width - minsize + 7;
1787                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
1788         }
1790         if (statelen && bufpos < view->width) {
1791                 string_format_from(buf, &bufpos, " %s", state);
1792         }
1794         if (view == display[current_view])
1795                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
1796         else
1797                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
1799         mvwaddnstr(view->title, 0, 0, buf, bufpos);
1800         wclrtoeol(view->title);
1801         wmove(view->title, 0, view->width - 1);
1803         if (input_mode)
1804                 wnoutrefresh(view->title);
1805         else
1806                 wrefresh(view->title);
1809 static void
1810 resize_display(void)
1812         int offset, i;
1813         struct view *base = display[0];
1814         struct view *view = display[1] ? display[1] : display[0];
1816         /* Setup window dimensions */
1818         getmaxyx(stdscr, base->height, base->width);
1820         /* Make room for the status window. */
1821         base->height -= 1;
1823         if (view != base) {
1824                 /* Horizontal split. */
1825                 view->width   = base->width;
1826                 view->height  = SCALE_SPLIT_VIEW(base->height);
1827                 base->height -= view->height;
1829                 /* Make room for the title bar. */
1830                 view->height -= 1;
1831         }
1833         /* Make room for the title bar. */
1834         base->height -= 1;
1836         offset = 0;
1838         foreach_displayed_view (view, i) {
1839                 if (!view->win) {
1840                         view->win = newwin(view->height, 0, offset, 0);
1841                         if (!view->win)
1842                                 die("Failed to create %s view", view->name);
1844                         scrollok(view->win, TRUE);
1846                         view->title = newwin(1, 0, offset + view->height, 0);
1847                         if (!view->title)
1848                                 die("Failed to create title window");
1850                 } else {
1851                         wresize(view->win, view->height, view->width);
1852                         mvwin(view->win,   offset, 0);
1853                         mvwin(view->title, offset + view->height, 0);
1854                 }
1856                 offset += view->height + 1;
1857         }
1860 static void
1861 redraw_display(void)
1863         struct view *view;
1864         int i;
1866         foreach_displayed_view (view, i) {
1867                 redraw_view(view);
1868                 update_view_title(view);
1869         }
1872 static void
1873 update_display_cursor(struct view *view)
1875         /* Move the cursor to the right-most column of the cursor line.
1876          *
1877          * XXX: This could turn out to be a bit expensive, but it ensures that
1878          * the cursor does not jump around. */
1879         if (view->lines) {
1880                 wmove(view->win, view->lineno - view->offset, view->width - 1);
1881                 wrefresh(view->win);
1882         }
1885 /*
1886  * Navigation
1887  */
1889 /* Scrolling backend */
1890 static void
1891 do_scroll_view(struct view *view, int lines)
1893         bool redraw_current_line = FALSE;
1895         /* The rendering expects the new offset. */
1896         view->offset += lines;
1898         assert(0 <= view->offset && view->offset < view->lines);
1899         assert(lines);
1901         /* Move current line into the view. */
1902         if (view->lineno < view->offset) {
1903                 view->lineno = view->offset;
1904                 redraw_current_line = TRUE;
1905         } else if (view->lineno >= view->offset + view->height) {
1906                 view->lineno = view->offset + view->height - 1;
1907                 redraw_current_line = TRUE;
1908         }
1910         assert(view->offset <= view->lineno && view->lineno < view->lines);
1912         /* Redraw the whole screen if scrolling is pointless. */
1913         if (view->height < ABS(lines)) {
1914                 redraw_view(view);
1916         } else {
1917                 int line = lines > 0 ? view->height - lines : 0;
1918                 int end = line + ABS(lines);
1920                 wscrl(view->win, lines);
1922                 for (; line < end; line++) {
1923                         if (!draw_view_line(view, line))
1924                                 break;
1925                 }
1927                 if (redraw_current_line)
1928                         draw_view_line(view, view->lineno - view->offset);
1929         }
1931         redrawwin(view->win);
1932         wrefresh(view->win);
1933         report("");
1936 /* Scroll frontend */
1937 static void
1938 scroll_view(struct view *view, enum request request)
1940         int lines = 1;
1942         assert(view_is_displayed(view));
1944         switch (request) {
1945         case REQ_SCROLL_PAGE_DOWN:
1946                 lines = view->height;
1947         case REQ_SCROLL_LINE_DOWN:
1948                 if (view->offset + lines > view->lines)
1949                         lines = view->lines - view->offset;
1951                 if (lines == 0 || view->offset + view->height >= view->lines) {
1952                         report("Cannot scroll beyond the last line");
1953                         return;
1954                 }
1955                 break;
1957         case REQ_SCROLL_PAGE_UP:
1958                 lines = view->height;
1959         case REQ_SCROLL_LINE_UP:
1960                 if (lines > view->offset)
1961                         lines = view->offset;
1963                 if (lines == 0) {
1964                         report("Cannot scroll beyond the first line");
1965                         return;
1966                 }
1968                 lines = -lines;
1969                 break;
1971         default:
1972                 die("request %d not handled in switch", request);
1973         }
1975         do_scroll_view(view, lines);
1978 /* Cursor moving */
1979 static void
1980 move_view(struct view *view, enum request request)
1982         int scroll_steps = 0;
1983         int steps;
1985         switch (request) {
1986         case REQ_MOVE_FIRST_LINE:
1987                 steps = -view->lineno;
1988                 break;
1990         case REQ_MOVE_LAST_LINE:
1991                 steps = view->lines - view->lineno - 1;
1992                 break;
1994         case REQ_MOVE_PAGE_UP:
1995                 steps = view->height > view->lineno
1996                       ? -view->lineno : -view->height;
1997                 break;
1999         case REQ_MOVE_PAGE_DOWN:
2000                 steps = view->lineno + view->height >= view->lines
2001                       ? view->lines - view->lineno - 1 : view->height;
2002                 break;
2004         case REQ_MOVE_UP:
2005                 steps = -1;
2006                 break;
2008         case REQ_MOVE_DOWN:
2009                 steps = 1;
2010                 break;
2012         default:
2013                 die("request %d not handled in switch", request);
2014         }
2016         if (steps <= 0 && view->lineno == 0) {
2017                 report("Cannot move beyond the first line");
2018                 return;
2020         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2021                 report("Cannot move beyond the last line");
2022                 return;
2023         }
2025         /* Move the current line */
2026         view->lineno += steps;
2027         assert(0 <= view->lineno && view->lineno < view->lines);
2029         /* Check whether the view needs to be scrolled */
2030         if (view->lineno < view->offset ||
2031             view->lineno >= view->offset + view->height) {
2032                 scroll_steps = steps;
2033                 if (steps < 0 && -steps > view->offset) {
2034                         scroll_steps = -view->offset;
2036                 } else if (steps > 0) {
2037                         if (view->lineno == view->lines - 1 &&
2038                             view->lines > view->height) {
2039                                 scroll_steps = view->lines - view->offset - 1;
2040                                 if (scroll_steps >= view->height)
2041                                         scroll_steps -= view->height - 1;
2042                         }
2043                 }
2044         }
2046         if (!view_is_displayed(view)) {
2047                 view->offset += scroll_steps;
2048                 assert(0 <= view->offset && view->offset < view->lines);
2049                 view->ops->select(view, &view->line[view->lineno]);
2050                 return;
2051         }
2053         /* Repaint the old "current" line if we be scrolling */
2054         if (ABS(steps) < view->height)
2055                 draw_view_line(view, view->lineno - steps - view->offset);
2057         if (scroll_steps) {
2058                 do_scroll_view(view, scroll_steps);
2059                 return;
2060         }
2062         /* Draw the current line */
2063         draw_view_line(view, view->lineno - view->offset);
2065         redrawwin(view->win);
2066         wrefresh(view->win);
2067         report("");
2071 /*
2072  * Searching
2073  */
2075 static void search_view(struct view *view, enum request request);
2077 static bool
2078 find_next_line(struct view *view, unsigned long lineno, struct line *line)
2080         assert(view_is_displayed(view));
2082         if (!view->ops->grep(view, line))
2083                 return FALSE;
2085         if (lineno - view->offset >= view->height) {
2086                 view->offset = lineno;
2087                 view->lineno = lineno;
2088                 redraw_view(view);
2090         } else {
2091                 unsigned long old_lineno = view->lineno - view->offset;
2093                 view->lineno = lineno;
2094                 draw_view_line(view, old_lineno);
2096                 draw_view_line(view, view->lineno - view->offset);
2097                 redrawwin(view->win);
2098                 wrefresh(view->win);
2099         }
2101         report("Line %ld matches '%s'", lineno + 1, view->grep);
2102         return TRUE;
2105 static void
2106 find_next(struct view *view, enum request request)
2108         unsigned long lineno = view->lineno;
2109         int direction;
2111         if (!*view->grep) {
2112                 if (!*opt_search)
2113                         report("No previous search");
2114                 else
2115                         search_view(view, request);
2116                 return;
2117         }
2119         switch (request) {
2120         case REQ_SEARCH:
2121         case REQ_FIND_NEXT:
2122                 direction = 1;
2123                 break;
2125         case REQ_SEARCH_BACK:
2126         case REQ_FIND_PREV:
2127                 direction = -1;
2128                 break;
2130         default:
2131                 return;
2132         }
2134         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2135                 lineno += direction;
2137         /* Note, lineno is unsigned long so will wrap around in which case it
2138          * will become bigger than view->lines. */
2139         for (; lineno < view->lines; lineno += direction) {
2140                 struct line *line = &view->line[lineno];
2142                 if (find_next_line(view, lineno, line))
2143                         return;
2144         }
2146         report("No match found for '%s'", view->grep);
2149 static void
2150 search_view(struct view *view, enum request request)
2152         int regex_err;
2154         if (view->regex) {
2155                 regfree(view->regex);
2156                 *view->grep = 0;
2157         } else {
2158                 view->regex = calloc(1, sizeof(*view->regex));
2159                 if (!view->regex)
2160                         return;
2161         }
2163         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2164         if (regex_err != 0) {
2165                 char buf[SIZEOF_STR] = "unknown error";
2167                 regerror(regex_err, view->regex, buf, sizeof(buf));
2168                 report("Search failed: %s", buf);
2169                 return;
2170         }
2172         string_copy(view->grep, opt_search);
2174         find_next(view, request);
2177 /*
2178  * Incremental updating
2179  */
2181 static void
2182 end_update(struct view *view, bool force)
2184         if (!view->pipe)
2185                 return;
2186         while (!view->ops->read(view, NULL))
2187                 if (!force)
2188                         return;
2189         set_nonblocking_input(FALSE);
2190         if (view->pipe == stdin)
2191                 fclose(view->pipe);
2192         else
2193                 pclose(view->pipe);
2194         view->pipe = NULL;
2197 static bool
2198 begin_update(struct view *view)
2200         if (opt_cmd[0]) {
2201                 string_copy(view->cmd, opt_cmd);
2202                 opt_cmd[0] = 0;
2203                 /* When running random commands, initially show the
2204                  * command in the title. However, it maybe later be
2205                  * overwritten if a commit line is selected. */
2206                 if (view == VIEW(REQ_VIEW_PAGER))
2207                         string_copy(view->ref, view->cmd);
2208                 else
2209                         view->ref[0] = 0;
2211         } else if (view == VIEW(REQ_VIEW_TREE)) {
2212                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
2213                 char path[SIZEOF_STR];
2215                 if (strcmp(view->vid, view->id))
2216                         opt_path[0] = path[0] = 0;
2217                 else if (sq_quote(path, 0, opt_path) >= sizeof(path))
2218                         return FALSE;
2220                 if (!string_format(view->cmd, format, view->id, path))
2221                         return FALSE;
2223         } else {
2224                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
2225                 const char *id = view->id;
2227                 if (!string_format(view->cmd, format, id, id, id, id, id))
2228                         return FALSE;
2230                 /* Put the current ref_* value to the view title ref
2231                  * member. This is needed by the blob view. Most other
2232                  * views sets it automatically after loading because the
2233                  * first line is a commit line. */
2234                 string_copy_rev(view->ref, view->id);
2235         }
2237         /* Special case for the pager view. */
2238         if (opt_pipe) {
2239                 view->pipe = opt_pipe;
2240                 opt_pipe = NULL;
2241         } else {
2242                 view->pipe = popen(view->cmd, "r");
2243         }
2245         if (!view->pipe)
2246                 return FALSE;
2248         set_nonblocking_input(TRUE);
2250         view->offset = 0;
2251         view->lines  = 0;
2252         view->lineno = 0;
2253         string_copy_rev(view->vid, view->id);
2255         if (view->line) {
2256                 int i;
2258                 for (i = 0; i < view->lines; i++)
2259                         if (view->line[i].data)
2260                                 free(view->line[i].data);
2262                 free(view->line);
2263                 view->line = NULL;
2264         }
2266         view->start_time = time(NULL);
2268         return TRUE;
2271 #define ITEM_CHUNK_SIZE 256
2272 static void *
2273 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2275         size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2276         size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2278         if (mem == NULL || num_chunks != num_chunks_new) {
2279                 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2280                 mem = realloc(mem, *size * item_size);
2281         }
2283         return mem;
2286 static struct line *
2287 realloc_lines(struct view *view, size_t line_size)
2289         size_t alloc = view->line_alloc;
2290         struct line *tmp = realloc_items(view->line, &alloc, line_size,
2291                                          sizeof(*view->line));
2293         if (!tmp)
2294                 return NULL;
2296         view->line = tmp;
2297         view->line_alloc = alloc;
2298         view->line_size = line_size;
2299         return view->line;
2302 static bool
2303 update_view(struct view *view)
2305         char in_buffer[BUFSIZ];
2306         char out_buffer[BUFSIZ * 2];
2307         char *line;
2308         /* The number of lines to read. If too low it will cause too much
2309          * redrawing (and possible flickering), if too high responsiveness
2310          * will suffer. */
2311         unsigned long lines = view->height;
2312         int redraw_from = -1;
2314         if (!view->pipe)
2315                 return TRUE;
2317         /* Only redraw if lines are visible. */
2318         if (view->offset + view->height >= view->lines)
2319                 redraw_from = view->lines - view->offset;
2321         /* FIXME: This is probably not perfect for backgrounded views. */
2322         if (!realloc_lines(view, view->lines + lines))
2323                 goto alloc_error;
2325         while ((line = fgets(in_buffer, sizeof(in_buffer), view->pipe))) {
2326                 size_t linelen = strlen(line);
2328                 if (linelen)
2329                         line[linelen - 1] = 0;
2331                 if (opt_iconv != ICONV_NONE) {
2332                         ICONV_CONST char *inbuf = line;
2333                         size_t inlen = linelen;
2335                         char *outbuf = out_buffer;
2336                         size_t outlen = sizeof(out_buffer);
2338                         size_t ret;
2340                         ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2341                         if (ret != (size_t) -1) {
2342                                 line = out_buffer;
2343                                 linelen = strlen(out_buffer);
2344                         }
2345                 }
2347                 if (!view->ops->read(view, line))
2348                         goto alloc_error;
2350                 if (lines-- == 1)
2351                         break;
2352         }
2354         {
2355                 int digits;
2357                 lines = view->lines;
2358                 for (digits = 0; lines; digits++)
2359                         lines /= 10;
2361                 /* Keep the displayed view in sync with line number scaling. */
2362                 if (digits != view->digits) {
2363                         view->digits = digits;
2364                         redraw_from = 0;
2365                 }
2366         }
2368         if (!view_is_displayed(view))
2369                 goto check_pipe;
2371         if (view == VIEW(REQ_VIEW_TREE)) {
2372                 /* Clear the view and redraw everything since the tree sorting
2373                  * might have rearranged things. */
2374                 redraw_view(view);
2376         } else if (redraw_from >= 0) {
2377                 /* If this is an incremental update, redraw the previous line
2378                  * since for commits some members could have changed when
2379                  * loading the main view. */
2380                 if (redraw_from > 0)
2381                         redraw_from--;
2383                 /* Since revision graph visualization requires knowledge
2384                  * about the parent commit, it causes a further one-off
2385                  * needed to be redrawn for incremental updates. */
2386                 if (redraw_from > 0 && opt_rev_graph)
2387                         redraw_from--;
2389                 /* Incrementally draw avoids flickering. */
2390                 redraw_view_from(view, redraw_from);
2391         }
2393         if (view == VIEW(REQ_VIEW_BLAME))
2394                 redraw_view_dirty(view);
2396         /* Update the title _after_ the redraw so that if the redraw picks up a
2397          * commit reference in view->ref it'll be available here. */
2398         update_view_title(view);
2400 check_pipe:
2401         if (ferror(view->pipe) && errno != 0) {
2402                 report("Failed to read: %s", strerror(errno));
2403                 end_update(view, TRUE);
2405         } else if (feof(view->pipe)) {
2406                 report("");
2407                 end_update(view, FALSE);
2408         }
2410         return TRUE;
2412 alloc_error:
2413         report("Allocation failure");
2414         end_update(view, TRUE);
2415         return FALSE;
2418 static struct line *
2419 add_line_data(struct view *view, void *data, enum line_type type)
2421         struct line *line = &view->line[view->lines++];
2423         memset(line, 0, sizeof(*line));
2424         line->type = type;
2425         line->data = data;
2427         return line;
2430 static struct line *
2431 add_line_text(struct view *view, char *data, enum line_type type)
2433         if (data)
2434                 data = strdup(data);
2436         return data ? add_line_data(view, data, type) : NULL;
2440 /*
2441  * View opening
2442  */
2444 enum open_flags {
2445         OPEN_DEFAULT = 0,       /* Use default view switching. */
2446         OPEN_SPLIT = 1,         /* Split current view. */
2447         OPEN_BACKGROUNDED = 2,  /* Backgrounded. */
2448         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
2449         OPEN_NOMAXIMIZE = 8     /* Do not maximize the current view. */
2450 };
2452 static void
2453 open_view(struct view *prev, enum request request, enum open_flags flags)
2455         bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2456         bool split = !!(flags & OPEN_SPLIT);
2457         bool reload = !!(flags & OPEN_RELOAD);
2458         bool nomaximize = !!(flags & OPEN_NOMAXIMIZE);
2459         struct view *view = VIEW(request);
2460         int nviews = displayed_views();
2461         struct view *base_view = display[0];
2463         if (view == prev && nviews == 1 && !reload) {
2464                 report("Already in %s view", view->name);
2465                 return;
2466         }
2468         if (view->git_dir && !opt_git_dir[0]) {
2469                 report("The %s view is disabled in pager view", view->name);
2470                 return;
2471         }
2473         if (split) {
2474                 display[1] = view;
2475                 if (!backgrounded)
2476                         current_view = 1;
2477         } else if (!nomaximize) {
2478                 /* Maximize the current view. */
2479                 memset(display, 0, sizeof(display));
2480                 current_view = 0;
2481                 display[current_view] = view;
2482         }
2484         /* Resize the view when switching between split- and full-screen,
2485          * or when switching between two different full-screen views. */
2486         if (nviews != displayed_views() ||
2487             (nviews == 1 && base_view != display[0]))
2488                 resize_display();
2490         if (view->pipe)
2491                 end_update(view, TRUE);
2493         if (view->ops->open) {
2494                 if (!view->ops->open(view)) {
2495                         report("Failed to load %s view", view->name);
2496                         return;
2497                 }
2499         } else if ((reload || strcmp(view->vid, view->id)) &&
2500                    !begin_update(view)) {
2501                 report("Failed to load %s view", view->name);
2502                 return;
2503         }
2505         if (split && prev->lineno - prev->offset >= prev->height) {
2506                 /* Take the title line into account. */
2507                 int lines = prev->lineno - prev->offset - prev->height + 1;
2509                 /* Scroll the view that was split if the current line is
2510                  * outside the new limited view. */
2511                 do_scroll_view(prev, lines);
2512         }
2514         if (prev && view != prev) {
2515                 if (split && !backgrounded) {
2516                         /* "Blur" the previous view. */
2517                         update_view_title(prev);
2518                 }
2520                 view->parent = prev;
2521         }
2523         if (view->pipe && view->lines == 0) {
2524                 /* Clear the old view and let the incremental updating refill
2525                  * the screen. */
2526                 werase(view->win);
2527                 report("");
2528         } else {
2529                 redraw_view(view);
2530                 report("");
2531         }
2533         /* If the view is backgrounded the above calls to report()
2534          * won't redraw the view title. */
2535         if (backgrounded)
2536                 update_view_title(view);
2539 static void
2540 run_confirm(const char *cmd, const char *prompt)
2542         if (prompt_yesno(prompt)) {
2543                 system(cmd);
2544         }
2547 static void
2548 open_external_viewer(const char *cmd)
2550         def_prog_mode();           /* save current tty modes */
2551         endwin();                  /* restore original tty modes */
2552         system(cmd);
2553         fprintf(stderr, "Press Enter to continue");
2554         getc(stdin);
2555         reset_prog_mode();
2556         redraw_display();
2559 static void
2560 open_mergetool(const char *file)
2562         char cmd[SIZEOF_STR];
2563         char file_sq[SIZEOF_STR];
2565         if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2566             string_format(cmd, "git mergetool %s", file_sq)) {
2567                 open_external_viewer(cmd);
2568         }
2571 static void
2572 open_editor(bool from_root, const char *file)
2574         char cmd[SIZEOF_STR];
2575         char file_sq[SIZEOF_STR];
2576         char *editor;
2577         char *prefix = from_root ? opt_cdup : "";
2579         editor = getenv("GIT_EDITOR");
2580         if (!editor && *opt_editor)
2581                 editor = opt_editor;
2582         if (!editor)
2583                 editor = getenv("VISUAL");
2584         if (!editor)
2585                 editor = getenv("EDITOR");
2586         if (!editor)
2587                 editor = "vi";
2589         if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2590             string_format(cmd, "%s %s%s", editor, prefix, file_sq)) {
2591                 open_external_viewer(cmd);
2592         }
2595 static void
2596 open_run_request(enum request request)
2598         struct run_request *req = get_run_request(request);
2599         char buf[SIZEOF_STR * 2];
2600         size_t bufpos;
2601         char *cmd;
2603         if (!req) {
2604                 report("Unknown run request");
2605                 return;
2606         }
2608         bufpos = 0;
2609         cmd = req->cmd;
2611         while (cmd) {
2612                 char *next = strstr(cmd, "%(");
2613                 int len = next - cmd;
2614                 char *value;
2616                 if (!next) {
2617                         len = strlen(cmd);
2618                         value = "";
2620                 } else if (!strncmp(next, "%(head)", 7)) {
2621                         value = ref_head;
2623                 } else if (!strncmp(next, "%(commit)", 9)) {
2624                         value = ref_commit;
2626                 } else if (!strncmp(next, "%(blob)", 7)) {
2627                         value = ref_blob;
2629                 } else {
2630                         report("Unknown replacement in run request: `%s`", req->cmd);
2631                         return;
2632                 }
2634                 if (!string_format_from(buf, &bufpos, "%.*s%s", len, cmd, value))
2635                         return;
2637                 if (next)
2638                         next = strchr(next, ')') + 1;
2639                 cmd = next;
2640         }
2642         open_external_viewer(buf);
2645 /*
2646  * User request switch noodle
2647  */
2649 static int
2650 view_driver(struct view *view, enum request request)
2652         int i;
2654         if (request == REQ_NONE) {
2655                 doupdate();
2656                 return TRUE;
2657         }
2659         if (request > REQ_NONE) {
2660                 open_run_request(request);
2661                 /* FIXME: When all views can refresh always do this. */
2662                 if (view == VIEW(REQ_VIEW_STATUS) ||
2663                     view == VIEW(REQ_VIEW_MAIN) ||
2664                     view == VIEW(REQ_VIEW_STAGE))
2665                         request = REQ_REFRESH;
2666                 else
2667                         return TRUE;
2668         }
2670         if (view && view->lines) {
2671                 request = view->ops->request(view, request, &view->line[view->lineno]);
2672                 if (request == REQ_NONE)
2673                         return TRUE;
2674         }
2676         switch (request) {
2677         case REQ_MOVE_UP:
2678         case REQ_MOVE_DOWN:
2679         case REQ_MOVE_PAGE_UP:
2680         case REQ_MOVE_PAGE_DOWN:
2681         case REQ_MOVE_FIRST_LINE:
2682         case REQ_MOVE_LAST_LINE:
2683                 move_view(view, request);
2684                 break;
2686         case REQ_SCROLL_LINE_DOWN:
2687         case REQ_SCROLL_LINE_UP:
2688         case REQ_SCROLL_PAGE_DOWN:
2689         case REQ_SCROLL_PAGE_UP:
2690                 scroll_view(view, request);
2691                 break;
2693         case REQ_VIEW_BLAME:
2694                 if (!opt_file[0]) {
2695                         report("No file chosen, press %s to open tree view",
2696                                get_key(REQ_VIEW_TREE));
2697                         break;
2698                 }
2699                 open_view(view, request, OPEN_DEFAULT);
2700                 break;
2702         case REQ_VIEW_BLOB:
2703                 if (!ref_blob[0]) {
2704                         report("No file chosen, press %s to open tree view",
2705                                get_key(REQ_VIEW_TREE));
2706                         break;
2707                 }
2708                 open_view(view, request, OPEN_DEFAULT);
2709                 break;
2711         case REQ_VIEW_PAGER:
2712                 if (!opt_pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
2713                         report("No pager content, press %s to run command from prompt",
2714                                get_key(REQ_PROMPT));
2715                         break;
2716                 }
2717                 open_view(view, request, OPEN_DEFAULT);
2718                 break;
2720         case REQ_VIEW_STAGE:
2721                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
2722                         report("No stage content, press %s to open the status view and choose file",
2723                                get_key(REQ_VIEW_STATUS));
2724                         break;
2725                 }
2726                 open_view(view, request, OPEN_DEFAULT);
2727                 break;
2729         case REQ_VIEW_STATUS:
2730                 if (opt_is_inside_work_tree == FALSE) {
2731                         report("The status view requires a working tree");
2732                         break;
2733                 }
2734                 open_view(view, request, OPEN_DEFAULT);
2735                 break;
2737         case REQ_VIEW_MAIN:
2738         case REQ_VIEW_DIFF:
2739         case REQ_VIEW_LOG:
2740         case REQ_VIEW_TREE:
2741         case REQ_VIEW_HELP:
2742                 open_view(view, request, OPEN_DEFAULT);
2743                 break;
2745         case REQ_NEXT:
2746         case REQ_PREVIOUS:
2747                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
2749                 if ((view == VIEW(REQ_VIEW_DIFF) &&
2750                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
2751                    (view == VIEW(REQ_VIEW_DIFF) &&
2752                      view->parent == VIEW(REQ_VIEW_BLAME)) ||
2753                    (view == VIEW(REQ_VIEW_STAGE) &&
2754                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
2755                    (view == VIEW(REQ_VIEW_BLOB) &&
2756                      view->parent == VIEW(REQ_VIEW_TREE))) {
2757                         int line;
2759                         view = view->parent;
2760                         line = view->lineno;
2761                         move_view(view, request);
2762                         if (view_is_displayed(view))
2763                                 update_view_title(view);
2764                         if (line != view->lineno)
2765                                 view->ops->request(view, REQ_ENTER,
2766                                                    &view->line[view->lineno]);
2768                 } else {
2769                         move_view(view, request);
2770                 }
2771                 break;
2773         case REQ_VIEW_NEXT:
2774         {
2775                 int nviews = displayed_views();
2776                 int next_view = (current_view + 1) % nviews;
2778                 if (next_view == current_view) {
2779                         report("Only one view is displayed");
2780                         break;
2781                 }
2783                 current_view = next_view;
2784                 /* Blur out the title of the previous view. */
2785                 update_view_title(view);
2786                 report("");
2787                 break;
2788         }
2789         case REQ_REFRESH:
2790                 report("Refreshing is not yet supported for the %s view", view->name);
2791                 break;
2793         case REQ_MAXIMIZE:
2794                 if (displayed_views() == 2)
2795                         open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
2796                 break;
2798         case REQ_TOGGLE_LINENO:
2799                 opt_line_number = !opt_line_number;
2800                 redraw_display();
2801                 break;
2803         case REQ_TOGGLE_DATE:
2804                 opt_date = !opt_date;
2805                 redraw_display();
2806                 break;
2808         case REQ_TOGGLE_AUTHOR:
2809                 opt_author = !opt_author;
2810                 redraw_display();
2811                 break;
2813         case REQ_TOGGLE_REV_GRAPH:
2814                 opt_rev_graph = !opt_rev_graph;
2815                 redraw_display();
2816                 break;
2818         case REQ_TOGGLE_REFS:
2819                 opt_show_refs = !opt_show_refs;
2820                 redraw_display();
2821                 break;
2823         case REQ_SEARCH:
2824         case REQ_SEARCH_BACK:
2825                 search_view(view, request);
2826                 break;
2828         case REQ_FIND_NEXT:
2829         case REQ_FIND_PREV:
2830                 find_next(view, request);
2831                 break;
2833         case REQ_STOP_LOADING:
2834                 for (i = 0; i < ARRAY_SIZE(views); i++) {
2835                         view = &views[i];
2836                         if (view->pipe)
2837                                 report("Stopped loading the %s view", view->name),
2838                         end_update(view, TRUE);
2839                 }
2840                 break;
2842         case REQ_SHOW_VERSION:
2843                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
2844                 return TRUE;
2846         case REQ_SCREEN_RESIZE:
2847                 resize_display();
2848                 /* Fall-through */
2849         case REQ_SCREEN_REDRAW:
2850                 redraw_display();
2851                 break;
2853         case REQ_EDIT:
2854                 report("Nothing to edit");
2855                 break;
2857         case REQ_ENTER:
2858                 report("Nothing to enter");
2859                 break;
2861         case REQ_VIEW_CLOSE:
2862                 /* XXX: Mark closed views by letting view->parent point to the
2863                  * view itself. Parents to closed view should never be
2864                  * followed. */
2865                 if (view->parent &&
2866                     view->parent->parent != view->parent) {
2867                         memset(display, 0, sizeof(display));
2868                         current_view = 0;
2869                         display[current_view] = view->parent;
2870                         view->parent = view;
2871                         resize_display();
2872                         redraw_display();
2873                         break;
2874                 }
2875                 /* Fall-through */
2876         case REQ_QUIT:
2877                 return FALSE;
2879         default:
2880                 /* An unknown key will show most commonly used commands. */
2881                 report("Unknown key, press 'h' for help");
2882                 return TRUE;
2883         }
2885         return TRUE;
2889 /*
2890  * Pager backend
2891  */
2893 static bool
2894 pager_draw(struct view *view, struct line *line, unsigned int lineno)
2896         char *text = line->data;
2898         if (opt_line_number && draw_lineno(view, lineno))
2899                 return TRUE;
2901         draw_text(view, line->type, text, TRUE);
2902         return TRUE;
2905 static bool
2906 add_describe_ref(char *buf, size_t *bufpos, char *commit_id, const char *sep)
2908         char refbuf[SIZEOF_STR];
2909         char *ref = NULL;
2910         FILE *pipe;
2912         if (!string_format(refbuf, "git describe %s 2>/dev/null", commit_id))
2913                 return TRUE;
2915         pipe = popen(refbuf, "r");
2916         if (!pipe)
2917                 return TRUE;
2919         if ((ref = fgets(refbuf, sizeof(refbuf), pipe)))
2920                 ref = chomp_string(ref);
2921         pclose(pipe);
2923         if (!ref || !*ref)
2924                 return TRUE;
2926         /* This is the only fatal call, since it can "corrupt" the buffer. */
2927         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
2928                 return FALSE;
2930         return TRUE;
2933 static void
2934 add_pager_refs(struct view *view, struct line *line)
2936         char buf[SIZEOF_STR];
2937         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
2938         struct ref **refs;
2939         size_t bufpos = 0, refpos = 0;
2940         const char *sep = "Refs: ";
2941         bool is_tag = FALSE;
2943         assert(line->type == LINE_COMMIT);
2945         refs = get_refs(commit_id);
2946         if (!refs) {
2947                 if (view == VIEW(REQ_VIEW_DIFF))
2948                         goto try_add_describe_ref;
2949                 return;
2950         }
2952         do {
2953                 struct ref *ref = refs[refpos];
2954                 char *fmt = ref->tag    ? "%s[%s]" :
2955                             ref->remote ? "%s<%s>" : "%s%s";
2957                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
2958                         return;
2959                 sep = ", ";
2960                 if (ref->tag)
2961                         is_tag = TRUE;
2962         } while (refs[refpos++]->next);
2964         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
2965 try_add_describe_ref:
2966                 /* Add <tag>-g<commit_id> "fake" reference. */
2967                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
2968                         return;
2969         }
2971         if (bufpos == 0)
2972                 return;
2974         if (!realloc_lines(view, view->line_size + 1))
2975                 return;
2977         add_line_text(view, buf, LINE_PP_REFS);
2980 static bool
2981 pager_read(struct view *view, char *data)
2983         struct line *line;
2985         if (!data)
2986                 return TRUE;
2988         line = add_line_text(view, data, get_line_type(data));
2989         if (!line)
2990                 return FALSE;
2992         if (line->type == LINE_COMMIT &&
2993             (view == VIEW(REQ_VIEW_DIFF) ||
2994              view == VIEW(REQ_VIEW_LOG)))
2995                 add_pager_refs(view, line);
2997         return TRUE;
3000 static enum request
3001 pager_request(struct view *view, enum request request, struct line *line)
3003         int split = 0;
3005         if (request != REQ_ENTER)
3006                 return request;
3008         if (line->type == LINE_COMMIT &&
3009            (view == VIEW(REQ_VIEW_LOG) ||
3010             view == VIEW(REQ_VIEW_PAGER))) {
3011                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3012                 split = 1;
3013         }
3015         /* Always scroll the view even if it was split. That way
3016          * you can use Enter to scroll through the log view and
3017          * split open each commit diff. */
3018         scroll_view(view, REQ_SCROLL_LINE_DOWN);
3020         /* FIXME: A minor workaround. Scrolling the view will call report("")
3021          * but if we are scrolling a non-current view this won't properly
3022          * update the view title. */
3023         if (split)
3024                 update_view_title(view);
3026         return REQ_NONE;
3029 static bool
3030 pager_grep(struct view *view, struct line *line)
3032         regmatch_t pmatch;
3033         char *text = line->data;
3035         if (!*text)
3036                 return FALSE;
3038         if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3039                 return FALSE;
3041         return TRUE;
3044 static void
3045 pager_select(struct view *view, struct line *line)
3047         if (line->type == LINE_COMMIT) {
3048                 char *text = (char *)line->data + STRING_SIZE("commit ");
3050                 if (view != VIEW(REQ_VIEW_PAGER))
3051                         string_copy_rev(view->ref, text);
3052                 string_copy_rev(ref_commit, text);
3053         }
3056 static struct view_ops pager_ops = {
3057         "line",
3058         NULL,
3059         pager_read,
3060         pager_draw,
3061         pager_request,
3062         pager_grep,
3063         pager_select,
3064 };
3067 /*
3068  * Help backend
3069  */
3071 static bool
3072 help_open(struct view *view)
3074         char buf[BUFSIZ];
3075         int lines = ARRAY_SIZE(req_info) + 2;
3076         int i;
3078         if (view->lines > 0)
3079                 return TRUE;
3081         for (i = 0; i < ARRAY_SIZE(req_info); i++)
3082                 if (!req_info[i].request)
3083                         lines++;
3085         lines += run_requests + 1;
3087         view->line = calloc(lines, sizeof(*view->line));
3088         if (!view->line)
3089                 return FALSE;
3091         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3093         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3094                 char *key;
3096                 if (req_info[i].request == REQ_NONE)
3097                         continue;
3099                 if (!req_info[i].request) {
3100                         add_line_text(view, "", LINE_DEFAULT);
3101                         add_line_text(view, req_info[i].help, LINE_DEFAULT);
3102                         continue;
3103                 }
3105                 key = get_key(req_info[i].request);
3106                 if (!*key)
3107                         key = "(no key defined)";
3109                 if (!string_format(buf, "    %-25s %s", key, req_info[i].help))
3110                         continue;
3112                 add_line_text(view, buf, LINE_DEFAULT);
3113         }
3115         if (run_requests) {
3116                 add_line_text(view, "", LINE_DEFAULT);
3117                 add_line_text(view, "External commands:", LINE_DEFAULT);
3118         }
3120         for (i = 0; i < run_requests; i++) {
3121                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3122                 char *key;
3124                 if (!req)
3125                         continue;
3127                 key = get_key_name(req->key);
3128                 if (!*key)
3129                         key = "(no key defined)";
3131                 if (!string_format(buf, "    %-10s %-14s `%s`",
3132                                    keymap_table[req->keymap].name,
3133                                    key, req->cmd))
3134                         continue;
3136                 add_line_text(view, buf, LINE_DEFAULT);
3137         }
3139         return TRUE;
3142 static struct view_ops help_ops = {
3143         "line",
3144         help_open,
3145         NULL,
3146         pager_draw,
3147         pager_request,
3148         pager_grep,
3149         pager_select,
3150 };
3153 /*
3154  * Tree backend
3155  */
3157 struct tree_stack_entry {
3158         struct tree_stack_entry *prev;  /* Entry below this in the stack */
3159         unsigned long lineno;           /* Line number to restore */
3160         char *name;                     /* Position of name in opt_path */
3161 };
3163 /* The top of the path stack. */
3164 static struct tree_stack_entry *tree_stack = NULL;
3165 unsigned long tree_lineno = 0;
3167 static void
3168 pop_tree_stack_entry(void)
3170         struct tree_stack_entry *entry = tree_stack;
3172         tree_lineno = entry->lineno;
3173         entry->name[0] = 0;
3174         tree_stack = entry->prev;
3175         free(entry);
3178 static void
3179 push_tree_stack_entry(char *name, unsigned long lineno)
3181         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3182         size_t pathlen = strlen(opt_path);
3184         if (!entry)
3185                 return;
3187         entry->prev = tree_stack;
3188         entry->name = opt_path + pathlen;
3189         tree_stack = entry;
3191         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3192                 pop_tree_stack_entry();
3193                 return;
3194         }
3196         /* Move the current line to the first tree entry. */
3197         tree_lineno = 1;
3198         entry->lineno = lineno;
3201 /* Parse output from git-ls-tree(1):
3202  *
3203  * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3204  * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3205  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3206  * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3207  */
3209 #define SIZEOF_TREE_ATTR \
3210         STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3212 #define TREE_UP_FORMAT "040000 tree %s\t.."
3214 static int
3215 tree_compare_entry(enum line_type type1, char *name1,
3216                    enum line_type type2, char *name2)
3218         if (type1 != type2) {
3219                 if (type1 == LINE_TREE_DIR)
3220                         return -1;
3221                 return 1;
3222         }
3224         return strcmp(name1, name2);
3227 static char *
3228 tree_path(struct line *line)
3230         char *path = line->data;
3232         return path + SIZEOF_TREE_ATTR;
3235 static bool
3236 tree_read(struct view *view, char *text)
3238         size_t textlen = text ? strlen(text) : 0;
3239         char buf[SIZEOF_STR];
3240         unsigned long pos;
3241         enum line_type type;
3242         bool first_read = view->lines == 0;
3244         if (!text)
3245                 return TRUE;
3246         if (textlen <= SIZEOF_TREE_ATTR)
3247                 return FALSE;
3249         type = text[STRING_SIZE("100644 ")] == 't'
3250              ? LINE_TREE_DIR : LINE_TREE_FILE;
3252         if (first_read) {
3253                 /* Add path info line */
3254                 if (!string_format(buf, "Directory path /%s", opt_path) ||
3255                     !realloc_lines(view, view->line_size + 1) ||
3256                     !add_line_text(view, buf, LINE_DEFAULT))
3257                         return FALSE;
3259                 /* Insert "link" to parent directory. */
3260                 if (*opt_path) {
3261                         if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
3262                             !realloc_lines(view, view->line_size + 1) ||
3263                             !add_line_text(view, buf, LINE_TREE_DIR))
3264                                 return FALSE;
3265                 }
3266         }
3268         /* Strip the path part ... */
3269         if (*opt_path) {
3270                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3271                 size_t striplen = strlen(opt_path);
3272                 char *path = text + SIZEOF_TREE_ATTR;
3274                 if (pathlen > striplen)
3275                         memmove(path, path + striplen,
3276                                 pathlen - striplen + 1);
3277         }
3279         /* Skip "Directory ..." and ".." line. */
3280         for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
3281                 struct line *line = &view->line[pos];
3282                 char *path1 = tree_path(line);
3283                 char *path2 = text + SIZEOF_TREE_ATTR;
3284                 int cmp = tree_compare_entry(line->type, path1, type, path2);
3286                 if (cmp <= 0)
3287                         continue;
3289                 text = strdup(text);
3290                 if (!text)
3291                         return FALSE;
3293                 if (view->lines > pos)
3294                         memmove(&view->line[pos + 1], &view->line[pos],
3295                                 (view->lines - pos) * sizeof(*line));
3297                 line = &view->line[pos];
3298                 line->data = text;
3299                 line->type = type;
3300                 view->lines++;
3301                 return TRUE;
3302         }
3304         if (!add_line_text(view, text, type))
3305                 return FALSE;
3307         if (tree_lineno > view->lineno) {
3308                 view->lineno = tree_lineno;
3309                 tree_lineno = 0;
3310         }
3312         return TRUE;
3315 static enum request
3316 tree_request(struct view *view, enum request request, struct line *line)
3318         enum open_flags flags;
3320         if (request == REQ_VIEW_BLAME) {
3321                 char *filename = tree_path(line);
3323                 if (line->type == LINE_TREE_DIR) {
3324                         report("Cannot show blame for directory %s", opt_path);
3325                         return REQ_NONE;
3326                 }
3328                 string_copy(opt_ref, view->vid);
3329                 string_format(opt_file, "%s%s", opt_path, filename);
3330                 return request;
3331         }
3332         if (request == REQ_TREE_PARENT) {
3333                 if (*opt_path) {
3334                         /* fake 'cd  ..' */
3335                         request = REQ_ENTER;
3336                         line = &view->line[1];
3337                 } else {
3338                         /* quit view if at top of tree */
3339                         return REQ_VIEW_CLOSE;
3340                 }
3341         }
3342         if (request != REQ_ENTER)
3343                 return request;
3345         /* Cleanup the stack if the tree view is at a different tree. */
3346         while (!*opt_path && tree_stack)
3347                 pop_tree_stack_entry();
3349         switch (line->type) {
3350         case LINE_TREE_DIR:
3351                 /* Depending on whether it is a subdir or parent (updir?) link
3352                  * mangle the path buffer. */
3353                 if (line == &view->line[1] && *opt_path) {
3354                         pop_tree_stack_entry();
3356                 } else {
3357                         char *basename = tree_path(line);
3359                         push_tree_stack_entry(basename, view->lineno);
3360                 }
3362                 /* Trees and subtrees share the same ID, so they are not not
3363                  * unique like blobs. */
3364                 flags = OPEN_RELOAD;
3365                 request = REQ_VIEW_TREE;
3366                 break;
3368         case LINE_TREE_FILE:
3369                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3370                 request = REQ_VIEW_BLOB;
3371                 break;
3373         default:
3374                 return TRUE;
3375         }
3377         open_view(view, request, flags);
3378         if (request == REQ_VIEW_TREE) {
3379                 view->lineno = tree_lineno;
3380         }
3382         return REQ_NONE;
3385 static void
3386 tree_select(struct view *view, struct line *line)
3388         char *text = (char *)line->data + STRING_SIZE("100644 blob ");
3390         if (line->type == LINE_TREE_FILE) {
3391                 string_copy_rev(ref_blob, text);
3393         } else if (line->type != LINE_TREE_DIR) {
3394                 return;
3395         }
3397         string_copy_rev(view->ref, text);
3400 static struct view_ops tree_ops = {
3401         "file",
3402         NULL,
3403         tree_read,
3404         pager_draw,
3405         tree_request,
3406         pager_grep,
3407         tree_select,
3408 };
3410 static bool
3411 blob_read(struct view *view, char *line)
3413         if (!line)
3414                 return TRUE;
3415         return add_line_text(view, line, LINE_DEFAULT) != NULL;
3418 static struct view_ops blob_ops = {
3419         "line",
3420         NULL,
3421         blob_read,
3422         pager_draw,
3423         pager_request,
3424         pager_grep,
3425         pager_select,
3426 };
3428 /*
3429  * Blame backend
3430  *
3431  * Loading the blame view is a two phase job:
3432  *
3433  *  1. File content is read either using opt_file from the
3434  *     filesystem or using git-cat-file.
3435  *  2. Then blame information is incrementally added by
3436  *     reading output from git-blame.
3437  */
3439 struct blame_commit {
3440         char id[SIZEOF_REV];            /* SHA1 ID. */
3441         char title[128];                /* First line of the commit message. */
3442         char author[75];                /* Author of the commit. */
3443         struct tm time;                 /* Date from the author ident. */
3444         char filename[128];             /* Name of file. */
3445 };
3447 struct blame {
3448         struct blame_commit *commit;
3449         unsigned int header:1;
3450         char text[1];
3451 };
3453 #define BLAME_CAT_FILE_CMD "git cat-file blob %s:%s"
3454 #define BLAME_INCREMENTAL_CMD "git blame --incremental %s %s"
3456 static bool
3457 blame_open(struct view *view)
3459         char path[SIZEOF_STR];
3460         char ref[SIZEOF_STR] = "";
3462         if (sq_quote(path, 0, opt_file) >= sizeof(path))
3463                 return FALSE;
3465         if (*opt_ref && sq_quote(ref, 0, opt_ref) >= sizeof(ref))
3466                 return FALSE;
3468         if (*opt_ref) {
3469                 if (!string_format(view->cmd, BLAME_CAT_FILE_CMD, ref, path))
3470                         return FALSE;
3471         } else {
3472                 view->pipe = fopen(opt_file, "r");
3473                 if (!view->pipe &&
3474                     !string_format(view->cmd, BLAME_CAT_FILE_CMD, "HEAD", path))
3475                         return FALSE;
3476         }
3478         if (!view->pipe)
3479                 view->pipe = popen(view->cmd, "r");
3480         if (!view->pipe)
3481                 return FALSE;
3483         if (!string_format(view->cmd, BLAME_INCREMENTAL_CMD, ref, path))
3484                 return FALSE;
3486         string_format(view->ref, "%s ...", opt_file);
3487         string_copy_rev(view->vid, opt_file);
3488         set_nonblocking_input(TRUE);
3490         if (view->line) {
3491                 int i;
3493                 for (i = 0; i < view->lines; i++)
3494                         free(view->line[i].data);
3495                 free(view->line);
3496         }
3498         view->lines = view->line_alloc = view->line_size = view->lineno = 0;
3499         view->offset = view->lines  = view->lineno = 0;
3500         view->line = NULL;
3501         view->start_time = time(NULL);
3503         return TRUE;
3506 static struct blame_commit *
3507 get_blame_commit(struct view *view, const char *id)
3509         size_t i;
3511         for (i = 0; i < view->lines; i++) {
3512                 struct blame *blame = view->line[i].data;
3514                 if (!blame->commit)
3515                         continue;
3517                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
3518                         return blame->commit;
3519         }
3521         {
3522                 struct blame_commit *commit = calloc(1, sizeof(*commit));
3524                 if (commit)
3525                         string_ncopy(commit->id, id, SIZEOF_REV);
3526                 return commit;
3527         }
3530 static bool
3531 parse_number(char **posref, size_t *number, size_t min, size_t max)
3533         char *pos = *posref;
3535         *posref = NULL;
3536         pos = strchr(pos + 1, ' ');
3537         if (!pos || !isdigit(pos[1]))
3538                 return FALSE;
3539         *number = atoi(pos + 1);
3540         if (*number < min || *number > max)
3541                 return FALSE;
3543         *posref = pos;
3544         return TRUE;
3547 static struct blame_commit *
3548 parse_blame_commit(struct view *view, char *text, int *blamed)
3550         struct blame_commit *commit;
3551         struct blame *blame;
3552         char *pos = text + SIZEOF_REV - 1;
3553         size_t lineno;
3554         size_t group;
3556         if (strlen(text) <= SIZEOF_REV || *pos != ' ')
3557                 return NULL;
3559         if (!parse_number(&pos, &lineno, 1, view->lines) ||
3560             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
3561                 return NULL;
3563         commit = get_blame_commit(view, text);
3564         if (!commit)
3565                 return NULL;
3567         *blamed += group;
3568         while (group--) {
3569                 struct line *line = &view->line[lineno + group - 1];
3571                 blame = line->data;
3572                 blame->commit = commit;
3573                 blame->header = !group;
3574                 line->dirty = 1;
3575         }
3577         return commit;
3580 static bool
3581 blame_read_file(struct view *view, char *line)
3583         if (!line) {
3584                 FILE *pipe = NULL;
3586                 if (view->lines > 0)
3587                         pipe = popen(view->cmd, "r");
3588                 else if (!view->parent)
3589                         die("No blame exist for %s", view->vid);
3590                 view->cmd[0] = 0;
3591                 if (!pipe) {
3592                         report("Failed to load blame data");
3593                         return TRUE;
3594                 }
3596                 fclose(view->pipe);
3597                 view->pipe = pipe;
3598                 return FALSE;
3600         } else {
3601                 size_t linelen = strlen(line);
3602                 struct blame *blame = malloc(sizeof(*blame) + linelen);
3604                 blame->commit = NULL;
3605                 strncpy(blame->text, line, linelen);
3606                 blame->text[linelen] = 0;
3607                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
3608         }
3611 static bool
3612 match_blame_header(const char *name, char **line)
3614         size_t namelen = strlen(name);
3615         bool matched = !strncmp(name, *line, namelen);
3617         if (matched)
3618                 *line += namelen;
3620         return matched;
3623 static bool
3624 blame_read(struct view *view, char *line)
3626         static struct blame_commit *commit = NULL;
3627         static int blamed = 0;
3628         static time_t author_time;
3630         if (*view->cmd)
3631                 return blame_read_file(view, line);
3633         if (!line) {
3634                 /* Reset all! */
3635                 commit = NULL;
3636                 blamed = 0;
3637                 string_format(view->ref, "%s", view->vid);
3638                 if (view_is_displayed(view)) {
3639                         update_view_title(view);
3640                         redraw_view_from(view, 0);
3641                 }
3642                 return TRUE;
3643         }
3645         if (!commit) {
3646                 commit = parse_blame_commit(view, line, &blamed);
3647                 string_format(view->ref, "%s %2d%%", view->vid,
3648                               blamed * 100 / view->lines);
3650         } else if (match_blame_header("author ", &line)) {
3651                 string_ncopy(commit->author, line, strlen(line));
3653         } else if (match_blame_header("author-time ", &line)) {
3654                 author_time = (time_t) atol(line);
3656         } else if (match_blame_header("author-tz ", &line)) {
3657                 long tz;
3659                 tz  = ('0' - line[1]) * 60 * 60 * 10;
3660                 tz += ('0' - line[2]) * 60 * 60;
3661                 tz += ('0' - line[3]) * 60;
3662                 tz += ('0' - line[4]) * 60;
3664                 if (line[0] == '-')
3665                         tz = -tz;
3667                 author_time -= tz;
3668                 gmtime_r(&author_time, &commit->time);
3670         } else if (match_blame_header("summary ", &line)) {
3671                 string_ncopy(commit->title, line, strlen(line));
3673         } else if (match_blame_header("filename ", &line)) {
3674                 string_ncopy(commit->filename, line, strlen(line));
3675                 commit = NULL;
3676         }
3678         return TRUE;
3681 static bool
3682 blame_draw(struct view *view, struct line *line, unsigned int lineno)
3684         struct blame *blame = line->data;
3685         struct tm *time = NULL;
3686         char *id = NULL, *author = NULL;
3688         if (blame->commit && *blame->commit->filename) {
3689                 id = blame->commit->id;
3690                 author = blame->commit->author;
3691                 time = &blame->commit->time;
3692         }
3694         if (opt_date && draw_date(view, time))
3695                 return TRUE;
3697         if (opt_author &&
3698             draw_field(view, LINE_MAIN_AUTHOR, author, opt_author_cols, TRUE))
3699                 return TRUE;
3701         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
3702                 return TRUE;
3704         if (draw_lineno(view, lineno))
3705                 return TRUE;
3707         draw_text(view, LINE_DEFAULT, blame->text, TRUE);
3708         return TRUE;
3711 static enum request
3712 blame_request(struct view *view, enum request request, struct line *line)
3714         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3715         struct blame *blame = line->data;
3717         switch (request) {
3718         case REQ_ENTER:
3719                 if (!blame->commit) {
3720                         report("No commit loaded yet");
3721                         break;
3722                 }
3724                 if (!strcmp(blame->commit->id, NULL_ID)) {
3725                         char path[SIZEOF_STR];
3727                         if (sq_quote(path, 0, view->vid) >= sizeof(path))
3728                                 break;
3729                         string_format(opt_cmd, "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s 2>/dev/null", path);
3730                 }
3732                 open_view(view, REQ_VIEW_DIFF, flags);
3733                 break;
3735         default:
3736                 return request;
3737         }
3739         return REQ_NONE;
3742 static bool
3743 blame_grep(struct view *view, struct line *line)
3745         struct blame *blame = line->data;
3746         struct blame_commit *commit = blame->commit;
3747         regmatch_t pmatch;
3749 #define MATCH(text, on)                                                 \
3750         (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
3752         if (commit) {
3753                 char buf[DATE_COLS + 1];
3755                 if (MATCH(commit->title, 1) ||
3756                     MATCH(commit->author, opt_author) ||
3757                     MATCH(commit->id, opt_date))
3758                         return TRUE;
3760                 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
3761                     MATCH(buf, 1))
3762                         return TRUE;
3763         }
3765         return MATCH(blame->text, 1);
3767 #undef MATCH
3770 static void
3771 blame_select(struct view *view, struct line *line)
3773         struct blame *blame = line->data;
3774         struct blame_commit *commit = blame->commit;
3776         if (!commit)
3777                 return;
3779         if (!strcmp(commit->id, NULL_ID))
3780                 string_ncopy(ref_commit, "HEAD", 4);
3781         else
3782                 string_copy_rev(ref_commit, commit->id);
3785 static struct view_ops blame_ops = {
3786         "line",
3787         blame_open,
3788         blame_read,
3789         blame_draw,
3790         blame_request,
3791         blame_grep,
3792         blame_select,
3793 };
3795 /*
3796  * Status backend
3797  */
3799 struct status {
3800         char status;
3801         struct {
3802                 mode_t mode;
3803                 char rev[SIZEOF_REV];
3804                 char name[SIZEOF_STR];
3805         } old;
3806         struct {
3807                 mode_t mode;
3808                 char rev[SIZEOF_REV];
3809                 char name[SIZEOF_STR];
3810         } new;
3811 };
3813 static char status_onbranch[SIZEOF_STR];
3814 static struct status stage_status;
3815 static enum line_type stage_line_type;
3816 static size_t stage_chunks;
3817 static int *stage_chunk;
3819 /* This should work even for the "On branch" line. */
3820 static inline bool
3821 status_has_none(struct view *view, struct line *line)
3823         return line < view->line + view->lines && !line[1].data;
3826 /* Get fields from the diff line:
3827  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
3828  */
3829 static inline bool
3830 status_get_diff(struct status *file, char *buf, size_t bufsize)
3832         char *old_mode = buf +  1;
3833         char *new_mode = buf +  8;
3834         char *old_rev  = buf + 15;
3835         char *new_rev  = buf + 56;
3836         char *status   = buf + 97;
3838         if (bufsize < 99 ||
3839             old_mode[-1] != ':' ||
3840             new_mode[-1] != ' ' ||
3841             old_rev[-1]  != ' ' ||
3842             new_rev[-1]  != ' ' ||
3843             status[-1]   != ' ')
3844                 return FALSE;
3846         file->status = *status;
3848         string_copy_rev(file->old.rev, old_rev);
3849         string_copy_rev(file->new.rev, new_rev);
3851         file->old.mode = strtoul(old_mode, NULL, 8);
3852         file->new.mode = strtoul(new_mode, NULL, 8);
3854         file->old.name[0] = file->new.name[0] = 0;
3856         return TRUE;
3859 static bool
3860 status_run(struct view *view, const char cmd[], char status, enum line_type type)
3862         struct status *file = NULL;
3863         struct status *unmerged = NULL;
3864         char buf[SIZEOF_STR * 4];
3865         size_t bufsize = 0;
3866         FILE *pipe;
3868         pipe = popen(cmd, "r");
3869         if (!pipe)
3870                 return FALSE;
3872         add_line_data(view, NULL, type);
3874         while (!feof(pipe) && !ferror(pipe)) {
3875                 char *sep;
3876                 size_t readsize;
3878                 readsize = fread(buf + bufsize, 1, sizeof(buf) - bufsize, pipe);
3879                 if (!readsize)
3880                         break;
3881                 bufsize += readsize;
3883                 /* Process while we have NUL chars. */
3884                 while ((sep = memchr(buf, 0, bufsize))) {
3885                         size_t sepsize = sep - buf + 1;
3887                         if (!file) {
3888                                 if (!realloc_lines(view, view->line_size + 1))
3889                                         goto error_out;
3891                                 file = calloc(1, sizeof(*file));
3892                                 if (!file)
3893                                         goto error_out;
3895                                 add_line_data(view, file, type);
3896                         }
3898                         /* Parse diff info part. */
3899                         if (status) {
3900                                 file->status = status;
3901                                 if (status == 'A')
3902                                         string_copy(file->old.rev, NULL_ID);
3904                         } else if (!file->status) {
3905                                 if (!status_get_diff(file, buf, sepsize))
3906                                         goto error_out;
3908                                 bufsize -= sepsize;
3909                                 memmove(buf, sep + 1, bufsize);
3911                                 sep = memchr(buf, 0, bufsize);
3912                                 if (!sep)
3913                                         break;
3914                                 sepsize = sep - buf + 1;
3916                                 /* Collapse all 'M'odified entries that
3917                                  * follow a associated 'U'nmerged entry.
3918                                  */
3919                                 if (file->status == 'U') {
3920                                         unmerged = file;
3922                                 } else if (unmerged) {
3923                                         int collapse = !strcmp(buf, unmerged->new.name);
3925                                         unmerged = NULL;
3926                                         if (collapse) {
3927                                                 free(file);
3928                                                 view->lines--;
3929                                                 continue;
3930                                         }
3931                                 }
3932                         }
3934                         /* Grab the old name for rename/copy. */
3935                         if (!*file->old.name &&
3936                             (file->status == 'R' || file->status == 'C')) {
3937                                 sepsize = sep - buf + 1;
3938                                 string_ncopy(file->old.name, buf, sepsize);
3939                                 bufsize -= sepsize;
3940                                 memmove(buf, sep + 1, bufsize);
3942                                 sep = memchr(buf, 0, bufsize);
3943                                 if (!sep)
3944                                         break;
3945                                 sepsize = sep - buf + 1;
3946                         }
3948                         /* git-ls-files just delivers a NUL separated
3949                          * list of file names similar to the second half
3950                          * of the git-diff-* output. */
3951                         string_ncopy(file->new.name, buf, sepsize);
3952                         if (!*file->old.name)
3953                                 string_copy(file->old.name, file->new.name);
3954                         bufsize -= sepsize;
3955                         memmove(buf, sep + 1, bufsize);
3956                         file = NULL;
3957                 }
3958         }
3960         if (ferror(pipe)) {
3961 error_out:
3962                 pclose(pipe);
3963                 return FALSE;
3964         }
3966         if (!view->line[view->lines - 1].data)
3967                 add_line_data(view, NULL, LINE_STAT_NONE);
3969         pclose(pipe);
3970         return TRUE;
3973 /* Don't show unmerged entries in the staged section. */
3974 #define STATUS_DIFF_INDEX_CMD "git diff-index -z --diff-filter=ACDMRTXB --cached -M HEAD"
3975 #define STATUS_DIFF_FILES_CMD "git diff-files -z"
3976 #define STATUS_LIST_OTHER_CMD \
3977         "git ls-files -z --others --exclude-standard"
3978 #define STATUS_LIST_NO_HEAD_CMD \
3979         "git ls-files -z --cached --exclude-standard"
3981 #define STATUS_DIFF_INDEX_SHOW_CMD \
3982         "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s %s 2>/dev/null"
3984 #define STATUS_DIFF_FILES_SHOW_CMD \
3985         "git diff-files --root --patch-with-stat -C -M -- %s %s 2>/dev/null"
3987 #define STATUS_DIFF_NO_HEAD_SHOW_CMD \
3988         "git diff --no-color --patch-with-stat /dev/null %s 2>/dev/null"
3990 /* First parse staged info using git-diff-index(1), then parse unstaged
3991  * info using git-diff-files(1), and finally untracked files using
3992  * git-ls-files(1). */
3993 static bool
3994 status_open(struct view *view)
3996         unsigned long prev_lineno = view->lineno;
3997         size_t i;
3999         for (i = 0; i < view->lines; i++)
4000                 free(view->line[i].data);
4001         free(view->line);
4002         view->lines = view->line_alloc = view->line_size = view->lineno = 0;
4003         view->line = NULL;
4005         if (!realloc_lines(view, view->line_size + 7))
4006                 return FALSE;
4008         add_line_data(view, NULL, LINE_STAT_HEAD);
4009         if (opt_no_head)
4010                 string_copy(status_onbranch, "Initial commit");
4011         else if (!*opt_head)
4012                 string_copy(status_onbranch, "Not currently on any branch");
4013         else if (!string_format(status_onbranch, "On branch %s", opt_head))
4014                 return FALSE;
4016         system("git update-index -q --refresh >/dev/null 2>/dev/null");
4018         if (opt_no_head &&
4019             !status_run(view, STATUS_LIST_NO_HEAD_CMD, 'A', LINE_STAT_STAGED))
4020                 return FALSE;
4021         else if (!status_run(view, STATUS_DIFF_INDEX_CMD, 0, LINE_STAT_STAGED))
4022                 return FALSE;
4024         if (!status_run(view, STATUS_DIFF_FILES_CMD, 0, LINE_STAT_UNSTAGED) ||
4025             !status_run(view, STATUS_LIST_OTHER_CMD, '?', LINE_STAT_UNTRACKED))
4026                 return FALSE;
4028         /* If all went well restore the previous line number to stay in
4029          * the context or select a line with something that can be
4030          * updated. */
4031         if (prev_lineno >= view->lines)
4032                 prev_lineno = view->lines - 1;
4033         while (prev_lineno < view->lines && !view->line[prev_lineno].data)
4034                 prev_lineno++;
4035         while (prev_lineno > 0 && !view->line[prev_lineno].data)
4036                 prev_lineno--;
4038         /* If the above fails, always skip the "On branch" line. */
4039         if (prev_lineno < view->lines)
4040                 view->lineno = prev_lineno;
4041         else
4042                 view->lineno = 1;
4044         if (view->lineno < view->offset)
4045                 view->offset = view->lineno;
4046         else if (view->offset + view->height <= view->lineno)
4047                 view->offset = view->lineno - view->height + 1;
4049         return TRUE;
4052 static bool
4053 status_draw(struct view *view, struct line *line, unsigned int lineno)
4055         struct status *status = line->data;
4056         enum line_type type;
4057         char *text;
4059         if (!status) {
4060                 switch (line->type) {
4061                 case LINE_STAT_STAGED:
4062                         type = LINE_STAT_SECTION;
4063                         text = "Changes to be committed:";
4064                         break;
4066                 case LINE_STAT_UNSTAGED:
4067                         type = LINE_STAT_SECTION;
4068                         text = "Changed but not updated:";
4069                         break;
4071                 case LINE_STAT_UNTRACKED:
4072                         type = LINE_STAT_SECTION;
4073                         text = "Untracked files:";
4074                         break;
4076                 case LINE_STAT_NONE:
4077                         type = LINE_DEFAULT;
4078                         text = "    (no files)";
4079                         break;
4081                 case LINE_STAT_HEAD:
4082                         type = LINE_STAT_HEAD;
4083                         text = status_onbranch;
4084                         break;
4086                 default:
4087                         return FALSE;
4088                 }
4089         } else {
4090                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4092                 buf[0] = status->status;
4093                 if (draw_text(view, line->type, buf, TRUE))
4094                         return TRUE;
4095                 type = LINE_DEFAULT;
4096                 text = status->new.name;
4097         }
4099         draw_text(view, type, text, TRUE);
4100         return TRUE;
4103 static enum request
4104 status_enter(struct view *view, struct line *line)
4106         struct status *status = line->data;
4107         char oldpath[SIZEOF_STR] = "";
4108         char newpath[SIZEOF_STR] = "";
4109         char *info;
4110         size_t cmdsize = 0;
4111         enum open_flags split;
4113         if (line->type == LINE_STAT_NONE ||
4114             (!status && line[1].type == LINE_STAT_NONE)) {
4115                 report("No file to diff");
4116                 return REQ_NONE;
4117         }
4119         if (status) {
4120                 if (sq_quote(oldpath, 0, status->old.name) >= sizeof(oldpath))
4121                         return REQ_QUIT;
4122                 /* Diffs for unmerged entries are empty when pasing the
4123                  * new path, so leave it empty. */
4124                 if (status->status != 'U' &&
4125                     sq_quote(newpath, 0, status->new.name) >= sizeof(newpath))
4126                         return REQ_QUIT;
4127         }
4129         if (opt_cdup[0] &&
4130             line->type != LINE_STAT_UNTRACKED &&
4131             !string_format_from(opt_cmd, &cmdsize, "cd %s;", opt_cdup))
4132                 return REQ_QUIT;
4134         switch (line->type) {
4135         case LINE_STAT_STAGED:
4136                 if (opt_no_head) {
4137                         if (!string_format_from(opt_cmd, &cmdsize,
4138                                                 STATUS_DIFF_NO_HEAD_SHOW_CMD,
4139                                                 newpath))
4140                                 return REQ_QUIT;
4141                 } else {
4142                         if (!string_format_from(opt_cmd, &cmdsize,
4143                                                 STATUS_DIFF_INDEX_SHOW_CMD,
4144                                                 oldpath, newpath))
4145                                 return REQ_QUIT;
4146                 }
4148                 if (status)
4149                         info = "Staged changes to %s";
4150                 else
4151                         info = "Staged changes";
4152                 break;
4154         case LINE_STAT_UNSTAGED:
4155                 if (!string_format_from(opt_cmd, &cmdsize,
4156                                         STATUS_DIFF_FILES_SHOW_CMD, oldpath, newpath))
4157                         return REQ_QUIT;
4158                 if (status)
4159                         info = "Unstaged changes to %s";
4160                 else
4161                         info = "Unstaged changes";
4162                 break;
4164         case LINE_STAT_UNTRACKED:
4165                 if (opt_pipe)
4166                         return REQ_QUIT;
4168                 if (!status) {
4169                         report("No file to show");
4170                         return REQ_NONE;
4171                 }
4173                 opt_pipe = fopen(status->new.name, "r");
4174                 info = "Untracked file %s";
4175                 break;
4177         case LINE_STAT_HEAD:
4178                 return REQ_NONE;
4180         default:
4181                 die("line type %d not handled in switch", line->type);
4182         }
4184         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4185         open_view(view, REQ_VIEW_STAGE, OPEN_RELOAD | split);
4186         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4187                 if (status) {
4188                         stage_status = *status;
4189                 } else {
4190                         memset(&stage_status, 0, sizeof(stage_status));
4191                 }
4193                 stage_line_type = line->type;
4194                 stage_chunks = 0;
4195                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4196         }
4198         return REQ_NONE;
4201 static bool
4202 status_exists(struct status *status, enum line_type type)
4204         struct view *view = VIEW(REQ_VIEW_STATUS);
4205         struct line *line;
4207         for (line = view->line; line < view->line + view->lines; line++) {
4208                 struct status *pos = line->data;
4210                 if (line->type == type && pos &&
4211                     !strcmp(status->new.name, pos->new.name))
4212                         return TRUE;
4213         }
4215         return FALSE;
4219 static FILE *
4220 status_update_prepare(enum line_type type)
4222         char cmd[SIZEOF_STR];
4223         size_t cmdsize = 0;
4225         if (opt_cdup[0] &&
4226             type != LINE_STAT_UNTRACKED &&
4227             !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4228                 return NULL;
4230         switch (type) {
4231         case LINE_STAT_STAGED:
4232                 string_add(cmd, cmdsize, "git update-index -z --index-info");
4233                 break;
4235         case LINE_STAT_UNSTAGED:
4236         case LINE_STAT_UNTRACKED:
4237                 string_add(cmd, cmdsize, "git update-index -z --add --remove --stdin");
4238                 break;
4240         default:
4241                 die("line type %d not handled in switch", type);
4242         }
4244         return popen(cmd, "w");
4247 static bool
4248 status_update_write(FILE *pipe, struct status *status, enum line_type type)
4250         char buf[SIZEOF_STR];
4251         size_t bufsize = 0;
4252         size_t written = 0;
4254         switch (type) {
4255         case LINE_STAT_STAGED:
4256                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
4257                                         status->old.mode,
4258                                         status->old.rev,
4259                                         status->old.name, 0))
4260                         return FALSE;
4261                 break;
4263         case LINE_STAT_UNSTAGED:
4264         case LINE_STAT_UNTRACKED:
4265                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
4266                         return FALSE;
4267                 break;
4269         default:
4270                 die("line type %d not handled in switch", type);
4271         }
4273         while (!ferror(pipe) && written < bufsize) {
4274                 written += fwrite(buf + written, 1, bufsize - written, pipe);
4275         }
4277         return written == bufsize;
4280 static bool
4281 status_update_file(struct status *status, enum line_type type)
4283         FILE *pipe = status_update_prepare(type);
4284         bool result;
4286         if (!pipe)
4287                 return FALSE;
4289         result = status_update_write(pipe, status, type);
4290         pclose(pipe);
4291         return result;
4294 static bool
4295 status_update_files(struct view *view, struct line *line)
4297         FILE *pipe = status_update_prepare(line->type);
4298         bool result = TRUE;
4299         struct line *pos = view->line + view->lines;
4300         int files = 0;
4301         int file, done;
4303         if (!pipe)
4304                 return FALSE;
4306         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
4307                 files++;
4309         for (file = 0, done = 0; result && file < files; line++, file++) {
4310                 int almost_done = file * 100 / files;
4312                 if (almost_done > done) {
4313                         done = almost_done;
4314                         string_format(view->ref, "updating file %u of %u (%d%% done)",
4315                                       file, files, done);
4316                         update_view_title(view);
4317                 }
4318                 result = status_update_write(pipe, line->data, line->type);
4319         }
4321         pclose(pipe);
4322         return result;
4325 static bool
4326 status_update(struct view *view)
4328         struct line *line = &view->line[view->lineno];
4330         assert(view->lines);
4332         if (!line->data) {
4333                 /* This should work even for the "On branch" line. */
4334                 if (line < view->line + view->lines && !line[1].data) {
4335                         report("Nothing to update");
4336                         return FALSE;
4337                 }
4339                 if (!status_update_files(view, line + 1)) {
4340                         report("Failed to update file status");
4341                         return FALSE;
4342                 }
4344         } else if (!status_update_file(line->data, line->type)) {
4345                 report("Failed to update file status");
4346                 return FALSE;
4347         }
4349         return TRUE;
4352 static bool
4353 status_checkout(struct status *status, enum line_type type, bool has_next)
4355         if (!status || type != LINE_STAT_UNSTAGED) {
4356                 if (has_next) {
4357                         report("Nothing to checkout");
4358                 } else if (type == LINE_STAT_UNTRACKED) {
4359                         report("Cannot checkout untracked files");
4360                 } else if (type == LINE_STAT_STAGED) {
4361                         report("Cannot checkout staged files");
4362                 } else {
4363                         report("Cannot checkout multiple files");
4364                 }
4365                 return FALSE;
4367         } else {
4368                 char cmd[SIZEOF_STR];
4369                 char file_sq[SIZEOF_STR];
4371                 if (sq_quote(file_sq, 0, status->old.name) < sizeof(file_sq) &&
4372                     string_format(cmd, "git checkout %s%s", opt_cdup, file_sq)) {
4373                         run_confirm(cmd, "Are you sure you want to overwrite any changes?");
4374                 }
4376                 return TRUE;
4377         }
4380 static enum request
4381 status_request(struct view *view, enum request request, struct line *line)
4383         struct status *status = line->data;
4385         switch (request) {
4386         case REQ_STATUS_UPDATE:
4387                 if (!status_update(view))
4388                         return REQ_NONE;
4389                 break;
4391         case REQ_STATUS_CHECKOUT:
4392                 if (!status_checkout(status, line->type, status_has_none(view, line)))
4393                         return REQ_NONE;
4394                 break;
4396         case REQ_STATUS_MERGE:
4397                 if (!status || status->status != 'U') {
4398                         report("Merging only possible for files with unmerged status ('U').");
4399                         return REQ_NONE;
4400                 }
4401                 open_mergetool(status->new.name);
4402                 break;
4404         case REQ_EDIT:
4405                 if (!status)
4406                         return request;
4408                 open_editor(status->status != '?', status->new.name);
4409                 break;
4411         case REQ_VIEW_BLAME:
4412                 if (status) {
4413                         string_copy(opt_file, status->new.name);
4414                         opt_ref[0] = 0;
4415                 }
4416                 return request;
4418         case REQ_ENTER:
4419                 /* After returning the status view has been split to
4420                  * show the stage view. No further reloading is
4421                  * necessary. */
4422                 status_enter(view, line);
4423                 return REQ_NONE;
4425         case REQ_REFRESH:
4426                 /* Simply reload the view. */
4427                 break;
4429         default:
4430                 return request;
4431         }
4433         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
4435         return REQ_NONE;
4438 static void
4439 status_select(struct view *view, struct line *line)
4441         struct status *status = line->data;
4442         char file[SIZEOF_STR] = "all files";
4443         char *text;
4444         char *key;
4446         if (status && !string_format(file, "'%s'", status->new.name))
4447                 return;
4449         if (!status && line[1].type == LINE_STAT_NONE)
4450                 line++;
4452         switch (line->type) {
4453         case LINE_STAT_STAGED:
4454                 text = "Press %s to unstage %s for commit";
4455                 break;
4457         case LINE_STAT_UNSTAGED:
4458                 text = "Press %s to stage %s for commit";
4459                 break;
4461         case LINE_STAT_UNTRACKED:
4462                 text = "Press %s to stage %s for addition";
4463                 break;
4465         case LINE_STAT_HEAD:
4466         case LINE_STAT_NONE:
4467                 text = "Nothing to update";
4468                 break;
4470         default:
4471                 die("line type %d not handled in switch", line->type);
4472         }
4474         if (status && status->status == 'U') {
4475                 text = "Press %s to resolve conflict in %s";
4476                 key = get_key(REQ_STATUS_MERGE);
4478         } else {
4479                 key = get_key(REQ_STATUS_UPDATE);
4480         }
4482         string_format(view->ref, text, key, file);
4485 static bool
4486 status_grep(struct view *view, struct line *line)
4488         struct status *status = line->data;
4489         enum { S_STATUS, S_NAME, S_END } state;
4490         char buf[2] = "?";
4491         regmatch_t pmatch;
4493         if (!status)
4494                 return FALSE;
4496         for (state = S_STATUS; state < S_END; state++) {
4497                 char *text;
4499                 switch (state) {
4500                 case S_NAME:    text = status->new.name;        break;
4501                 case S_STATUS:
4502                         buf[0] = status->status;
4503                         text = buf;
4504                         break;
4506                 default:
4507                         return FALSE;
4508                 }
4510                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4511                         return TRUE;
4512         }
4514         return FALSE;
4517 static struct view_ops status_ops = {
4518         "file",
4519         status_open,
4520         NULL,
4521         status_draw,
4522         status_request,
4523         status_grep,
4524         status_select,
4525 };
4528 static bool
4529 stage_diff_line(FILE *pipe, struct line *line)
4531         char *buf = line->data;
4532         size_t bufsize = strlen(buf);
4533         size_t written = 0;
4535         while (!ferror(pipe) && written < bufsize) {
4536                 written += fwrite(buf + written, 1, bufsize - written, pipe);
4537         }
4539         fputc('\n', pipe);
4541         return written == bufsize;
4544 static bool
4545 stage_diff_write(FILE *pipe, struct line *line, struct line *end)
4547         while (line < end) {
4548                 if (!stage_diff_line(pipe, line++))
4549                         return FALSE;
4550                 if (line->type == LINE_DIFF_CHUNK ||
4551                     line->type == LINE_DIFF_HEADER)
4552                         break;
4553         }
4555         return TRUE;
4558 static struct line *
4559 stage_diff_find(struct view *view, struct line *line, enum line_type type)
4561         for (; view->line < line; line--)
4562                 if (line->type == type)
4563                         return line;
4565         return NULL;
4568 static bool
4569 stage_update_chunk(struct view *view, struct line *chunk)
4571         char cmd[SIZEOF_STR];
4572         size_t cmdsize = 0;
4573         struct line *diff_hdr;
4574         FILE *pipe;
4576         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
4577         if (!diff_hdr)
4578                 return FALSE;
4580         if (opt_cdup[0] &&
4581             !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4582                 return FALSE;
4584         if (!string_format_from(cmd, &cmdsize,
4585                                 "git apply --whitespace=nowarn --cached %s - && "
4586                                 "git update-index -q --unmerged --refresh 2>/dev/null",
4587                                 stage_line_type == LINE_STAT_STAGED ? "-R" : ""))
4588                 return FALSE;
4590         pipe = popen(cmd, "w");
4591         if (!pipe)
4592                 return FALSE;
4594         if (!stage_diff_write(pipe, diff_hdr, chunk) ||
4595             !stage_diff_write(pipe, chunk, view->line + view->lines))
4596                 chunk = NULL;
4598         pclose(pipe);
4600         return chunk ? TRUE : FALSE;
4603 static bool
4604 stage_update(struct view *view, struct line *line)
4606         struct line *chunk = NULL;
4608         if (!opt_no_head && stage_line_type != LINE_STAT_UNTRACKED)
4609                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4611         if (chunk) {
4612                 if (!stage_update_chunk(view, chunk)) {
4613                         report("Failed to apply chunk");
4614                         return FALSE;
4615                 }
4617         } else if (!stage_status.status) {
4618                 view = VIEW(REQ_VIEW_STATUS);
4620                 for (line = view->line; line < view->line + view->lines; line++)
4621                         if (line->type == stage_line_type)
4622                                 break;
4624                 if (!status_update_files(view, line + 1)) {
4625                         report("Failed to update files");
4626                         return FALSE;
4627                 }
4629         } else if (!status_update_file(&stage_status, stage_line_type)) {
4630                 report("Failed to update file");
4631                 return FALSE;
4632         }
4634         return TRUE;
4637 static void
4638 stage_next(struct view *view, struct line *line)
4640         int i;
4642         if (!stage_chunks) {
4643                 static size_t alloc = 0;
4644                 int *tmp;
4646                 for (line = view->line; line < view->line + view->lines; line++) {
4647                         if (line->type != LINE_DIFF_CHUNK)
4648                                 continue;
4650                         tmp = realloc_items(stage_chunk, &alloc,
4651                                             stage_chunks, sizeof(*tmp));
4652                         if (!tmp) {
4653                                 report("Allocation failure");
4654                                 return;
4655                         }
4657                         stage_chunk = tmp;
4658                         stage_chunk[stage_chunks++] = line - view->line;
4659                 }
4660         }
4662         for (i = 0; i < stage_chunks; i++) {
4663                 if (stage_chunk[i] > view->lineno) {
4664                         do_scroll_view(view, stage_chunk[i] - view->lineno);
4665                         report("Chunk %d of %d", i + 1, stage_chunks);
4666                         return;
4667                 }
4668         }
4670         report("No next chunk found");
4673 static enum request
4674 stage_request(struct view *view, enum request request, struct line *line)
4676         switch (request) {
4677         case REQ_STATUS_UPDATE:
4678                 if (!stage_update(view, line))
4679                         return REQ_NONE;
4680                 break;
4682         case REQ_STATUS_CHECKOUT:
4683                 if (!status_checkout(&stage_status, stage_line_type, FALSE))
4684                         return REQ_NONE;
4685                 break;
4687         case REQ_STAGE_NEXT:
4688                 if (stage_line_type == LINE_STAT_UNTRACKED) {
4689                         report("File is untracked; press %s to add",
4690                                get_key(REQ_STATUS_UPDATE));
4691                         return REQ_NONE;
4692                 }
4693                 stage_next(view, line);
4694                 return REQ_NONE;
4696         case REQ_EDIT:
4697                 if (!stage_status.new.name[0])
4698                         return request;
4700                 open_editor(stage_status.status != '?', stage_status.new.name);
4701                 break;
4703         case REQ_REFRESH:
4704                 /* Reload everything ... */
4705                 break;
4707         case REQ_VIEW_BLAME:
4708                 if (stage_status.new.name[0]) {
4709                         string_copy(opt_file, stage_status.new.name);
4710                         opt_ref[0] = 0;
4711                 }
4712                 return request;
4714         case REQ_ENTER:
4715                 return pager_request(view, request, line);
4717         default:
4718                 return request;
4719         }
4721         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD | OPEN_NOMAXIMIZE);
4723         /* Check whether the staged entry still exists, and close the
4724          * stage view if it doesn't. */
4725         if (!status_exists(&stage_status, stage_line_type))
4726                 return REQ_VIEW_CLOSE;
4728         if (stage_line_type == LINE_STAT_UNTRACKED)
4729                 opt_pipe = fopen(stage_status.new.name, "r");
4730         else
4731                 string_copy(opt_cmd, view->cmd);
4732         open_view(view, REQ_VIEW_STAGE, OPEN_RELOAD | OPEN_NOMAXIMIZE);
4734         return REQ_NONE;
4737 static struct view_ops stage_ops = {
4738         "line",
4739         NULL,
4740         pager_read,
4741         pager_draw,
4742         stage_request,
4743         pager_grep,
4744         pager_select,
4745 };
4748 /*
4749  * Revision graph
4750  */
4752 struct commit {
4753         char id[SIZEOF_REV];            /* SHA1 ID. */
4754         char title[128];                /* First line of the commit message. */
4755         char author[75];                /* Author of the commit. */
4756         struct tm time;                 /* Date from the author ident. */
4757         struct ref **refs;              /* Repository references. */
4758         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
4759         size_t graph_size;              /* The width of the graph array. */
4760         bool has_parents;               /* Rewritten --parents seen. */
4761 };
4763 /* Size of rev graph with no  "padding" columns */
4764 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
4766 struct rev_graph {
4767         struct rev_graph *prev, *next, *parents;
4768         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
4769         size_t size;
4770         struct commit *commit;
4771         size_t pos;
4772         unsigned int boundary:1;
4773 };
4775 /* Parents of the commit being visualized. */
4776 static struct rev_graph graph_parents[4];
4778 /* The current stack of revisions on the graph. */
4779 static struct rev_graph graph_stacks[4] = {
4780         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
4781         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
4782         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
4783         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
4784 };
4786 static inline bool
4787 graph_parent_is_merge(struct rev_graph *graph)
4789         return graph->parents->size > 1;
4792 static inline void
4793 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
4795         struct commit *commit = graph->commit;
4797         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
4798                 commit->graph[commit->graph_size++] = symbol;
4801 static void
4802 clear_rev_graph(struct rev_graph *graph)
4804         graph->boundary = 0;
4805         graph->size = graph->pos = 0;
4806         graph->commit = NULL;
4807         memset(graph->parents, 0, sizeof(*graph->parents));
4810 static void
4811 done_rev_graph(struct rev_graph *graph)
4813         if (graph_parent_is_merge(graph) &&
4814             graph->pos < graph->size - 1 &&
4815             graph->next->size == graph->size + graph->parents->size - 1) {
4816                 size_t i = graph->pos + graph->parents->size - 1;
4818                 graph->commit->graph_size = i * 2;
4819                 while (i < graph->next->size - 1) {
4820                         append_to_rev_graph(graph, ' ');
4821                         append_to_rev_graph(graph, '\\');
4822                         i++;
4823                 }
4824         }
4826         clear_rev_graph(graph);
4829 static void
4830 push_rev_graph(struct rev_graph *graph, char *parent)
4832         int i;
4834         /* "Collapse" duplicate parents lines.
4835          *
4836          * FIXME: This needs to also update update the drawn graph but
4837          * for now it just serves as a method for pruning graph lines. */
4838         for (i = 0; i < graph->size; i++)
4839                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
4840                         return;
4842         if (graph->size < SIZEOF_REVITEMS) {
4843                 string_copy_rev(graph->rev[graph->size++], parent);
4844         }
4847 static chtype
4848 get_rev_graph_symbol(struct rev_graph *graph)
4850         chtype symbol;
4852         if (graph->boundary)
4853                 symbol = REVGRAPH_BOUND;
4854         else if (graph->parents->size == 0)
4855                 symbol = REVGRAPH_INIT;
4856         else if (graph_parent_is_merge(graph))
4857                 symbol = REVGRAPH_MERGE;
4858         else if (graph->pos >= graph->size)
4859                 symbol = REVGRAPH_BRANCH;
4860         else
4861                 symbol = REVGRAPH_COMMIT;
4863         return symbol;
4866 static void
4867 draw_rev_graph(struct rev_graph *graph)
4869         struct rev_filler {
4870                 chtype separator, line;
4871         };
4872         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
4873         static struct rev_filler fillers[] = {
4874                 { ' ',  '|' },
4875                 { '`',  '.' },
4876                 { '\'', ' ' },
4877                 { '/',  ' ' },
4878         };
4879         chtype symbol = get_rev_graph_symbol(graph);
4880         struct rev_filler *filler;
4881         size_t i;
4883         if (opt_line_graphics)
4884                 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
4886         filler = &fillers[DEFAULT];
4888         for (i = 0; i < graph->pos; i++) {
4889                 append_to_rev_graph(graph, filler->line);
4890                 if (graph_parent_is_merge(graph->prev) &&
4891                     graph->prev->pos == i)
4892                         filler = &fillers[RSHARP];
4894                 append_to_rev_graph(graph, filler->separator);
4895         }
4897         /* Place the symbol for this revision. */
4898         append_to_rev_graph(graph, symbol);
4900         if (graph->prev->size > graph->size)
4901                 filler = &fillers[RDIAG];
4902         else
4903                 filler = &fillers[DEFAULT];
4905         i++;
4907         for (; i < graph->size; i++) {
4908                 append_to_rev_graph(graph, filler->separator);
4909                 append_to_rev_graph(graph, filler->line);
4910                 if (graph_parent_is_merge(graph->prev) &&
4911                     i < graph->prev->pos + graph->parents->size)
4912                         filler = &fillers[RSHARP];
4913                 if (graph->prev->size > graph->size)
4914                         filler = &fillers[LDIAG];
4915         }
4917         if (graph->prev->size > graph->size) {
4918                 append_to_rev_graph(graph, filler->separator);
4919                 if (filler->line != ' ')
4920                         append_to_rev_graph(graph, filler->line);
4921         }
4924 /* Prepare the next rev graph */
4925 static void
4926 prepare_rev_graph(struct rev_graph *graph)
4928         size_t i;
4930         /* First, traverse all lines of revisions up to the active one. */
4931         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
4932                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
4933                         break;
4935                 push_rev_graph(graph->next, graph->rev[graph->pos]);
4936         }
4938         /* Interleave the new revision parent(s). */
4939         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
4940                 push_rev_graph(graph->next, graph->parents->rev[i]);
4942         /* Lastly, put any remaining revisions. */
4943         for (i = graph->pos + 1; i < graph->size; i++)
4944                 push_rev_graph(graph->next, graph->rev[i]);
4947 static void
4948 update_rev_graph(struct rev_graph *graph)
4950         /* If this is the finalizing update ... */
4951         if (graph->commit)
4952                 prepare_rev_graph(graph);
4954         /* Graph visualization needs a one rev look-ahead,
4955          * so the first update doesn't visualize anything. */
4956         if (!graph->prev->commit)
4957                 return;
4959         draw_rev_graph(graph->prev);
4960         done_rev_graph(graph->prev->prev);
4964 /*
4965  * Main view backend
4966  */
4968 static bool
4969 main_draw(struct view *view, struct line *line, unsigned int lineno)
4971         struct commit *commit = line->data;
4973         if (!*commit->author)
4974                 return FALSE;
4976         if (opt_date && draw_date(view, &commit->time))
4977                 return TRUE;
4979         if (opt_author &&
4980             draw_field(view, LINE_MAIN_AUTHOR, commit->author, opt_author_cols, TRUE))
4981                 return TRUE;
4983         if (opt_rev_graph && commit->graph_size &&
4984             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
4985                 return TRUE;
4987         if (opt_show_refs && commit->refs) {
4988                 size_t i = 0;
4990                 do {
4991                         enum line_type type;
4993                         if (commit->refs[i]->head)
4994                                 type = LINE_MAIN_HEAD;
4995                         else if (commit->refs[i]->ltag)
4996                                 type = LINE_MAIN_LOCAL_TAG;
4997                         else if (commit->refs[i]->tag)
4998                                 type = LINE_MAIN_TAG;
4999                         else if (commit->refs[i]->tracked)
5000                                 type = LINE_MAIN_TRACKED;
5001                         else if (commit->refs[i]->remote)
5002                                 type = LINE_MAIN_REMOTE;
5003                         else
5004                                 type = LINE_MAIN_REF;
5006                         if (draw_text(view, type, "[", TRUE) ||
5007                             draw_text(view, type, commit->refs[i]->name, TRUE) ||
5008                             draw_text(view, type, "]", TRUE))
5009                                 return TRUE;
5011                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
5012                                 return TRUE;
5013                 } while (commit->refs[i++]->next);
5014         }
5016         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
5017         return TRUE;
5020 /* Reads git log --pretty=raw output and parses it into the commit struct. */
5021 static bool
5022 main_read(struct view *view, char *line)
5024         static struct rev_graph *graph = graph_stacks;
5025         enum line_type type;
5026         struct commit *commit;
5028         if (!line) {
5029                 int i;
5031                 if (!view->lines && !view->parent)
5032                         die("No revisions match the given arguments.");
5033                 if (view->lines > 0) {
5034                         commit = view->line[view->lines - 1].data;
5035                         if (!*commit->author) {
5036                                 view->lines--;
5037                                 free(commit);
5038                                 graph->commit = NULL;
5039                         }
5040                 }
5041                 update_rev_graph(graph);
5043                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
5044                         clear_rev_graph(&graph_stacks[i]);
5045                 return TRUE;
5046         }
5048         type = get_line_type(line);
5049         if (type == LINE_COMMIT) {
5050                 commit = calloc(1, sizeof(struct commit));
5051                 if (!commit)
5052                         return FALSE;
5054                 line += STRING_SIZE("commit ");
5055                 if (*line == '-') {
5056                         graph->boundary = 1;
5057                         line++;
5058                 }
5060                 string_copy_rev(commit->id, line);
5061                 commit->refs = get_refs(commit->id);
5062                 graph->commit = commit;
5063                 add_line_data(view, commit, LINE_MAIN_COMMIT);
5065                 while ((line = strchr(line, ' '))) {
5066                         line++;
5067                         push_rev_graph(graph->parents, line);
5068                         commit->has_parents = TRUE;
5069                 }
5070                 return TRUE;
5071         }
5073         if (!view->lines)
5074                 return TRUE;
5075         commit = view->line[view->lines - 1].data;
5077         switch (type) {
5078         case LINE_PARENT:
5079                 if (commit->has_parents)
5080                         break;
5081                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5082                 break;
5084         case LINE_AUTHOR:
5085         {
5086                 /* Parse author lines where the name may be empty:
5087                  *      author  <email@address.tld> 1138474660 +0100
5088                  */
5089                 char *ident = line + STRING_SIZE("author ");
5090                 char *nameend = strchr(ident, '<');
5091                 char *emailend = strchr(ident, '>');
5093                 if (!nameend || !emailend)
5094                         break;
5096                 update_rev_graph(graph);
5097                 graph = graph->next;
5099                 *nameend = *emailend = 0;
5100                 ident = chomp_string(ident);
5101                 if (!*ident) {
5102                         ident = chomp_string(nameend + 1);
5103                         if (!*ident)
5104                                 ident = "Unknown";
5105                 }
5107                 string_ncopy(commit->author, ident, strlen(ident));
5109                 /* Parse epoch and timezone */
5110                 if (emailend[1] == ' ') {
5111                         char *secs = emailend + 2;
5112                         char *zone = strchr(secs, ' ');
5113                         time_t time = (time_t) atol(secs);
5115                         if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
5116                                 long tz;
5118                                 zone++;
5119                                 tz  = ('0' - zone[1]) * 60 * 60 * 10;
5120                                 tz += ('0' - zone[2]) * 60 * 60;
5121                                 tz += ('0' - zone[3]) * 60;
5122                                 tz += ('0' - zone[4]) * 60;
5124                                 if (zone[0] == '-')
5125                                         tz = -tz;
5127                                 time -= tz;
5128                         }
5130                         gmtime_r(&time, &commit->time);
5131                 }
5132                 break;
5133         }
5134         default:
5135                 /* Fill in the commit title if it has not already been set. */
5136                 if (commit->title[0])
5137                         break;
5139                 /* Require titles to start with a non-space character at the
5140                  * offset used by git log. */
5141                 if (strncmp(line, "    ", 4))
5142                         break;
5143                 line += 4;
5144                 /* Well, if the title starts with a whitespace character,
5145                  * try to be forgiving.  Otherwise we end up with no title. */
5146                 while (isspace(*line))
5147                         line++;
5148                 if (*line == '\0')
5149                         break;
5150                 /* FIXME: More graceful handling of titles; append "..." to
5151                  * shortened titles, etc. */
5153                 string_ncopy(commit->title, line, strlen(line));
5154         }
5156         return TRUE;
5159 static enum request
5160 main_request(struct view *view, enum request request, struct line *line)
5162         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5164         switch (request) {
5165         case REQ_ENTER:
5166                 open_view(view, REQ_VIEW_DIFF, flags);
5167                 break;
5168         case REQ_REFRESH:
5169                 string_copy(opt_cmd, view->cmd);
5170                 open_view(view, REQ_VIEW_MAIN, OPEN_RELOAD);
5171                 break;
5172         default:
5173                 return request;
5174         }
5176         return REQ_NONE;
5179 static bool
5180 grep_refs(struct ref **refs, regex_t *regex)
5182         regmatch_t pmatch;
5183         size_t i = 0;
5185         if (!refs)
5186                 return FALSE;
5187         do {
5188                 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
5189                         return TRUE;
5190         } while (refs[i++]->next);
5192         return FALSE;
5195 static bool
5196 main_grep(struct view *view, struct line *line)
5198         struct commit *commit = line->data;
5199         enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
5200         char buf[DATE_COLS + 1];
5201         regmatch_t pmatch;
5203         for (state = S_TITLE; state < S_END; state++) {
5204                 char *text;
5206                 switch (state) {
5207                 case S_TITLE:   text = commit->title;   break;
5208                 case S_AUTHOR:
5209                         if (!opt_author)
5210                                 continue;
5211                         text = commit->author;
5212                         break;
5213                 case S_DATE:
5214                         if (!opt_date)
5215                                 continue;
5216                         if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5217                                 continue;
5218                         text = buf;
5219                         break;
5220                 case S_REFS:
5221                         if (!opt_show_refs)
5222                                 continue;
5223                         if (grep_refs(commit->refs, view->regex) == TRUE)
5224                                 return TRUE;
5225                         continue;
5226                 default:
5227                         return FALSE;
5228                 }
5230                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5231                         return TRUE;
5232         }
5234         return FALSE;
5237 static void
5238 main_select(struct view *view, struct line *line)
5240         struct commit *commit = line->data;
5242         string_copy_rev(view->ref, commit->id);
5243         string_copy_rev(ref_commit, view->ref);
5246 static struct view_ops main_ops = {
5247         "commit",
5248         NULL,
5249         main_read,
5250         main_draw,
5251         main_request,
5252         main_grep,
5253         main_select,
5254 };
5257 /*
5258  * Unicode / UTF-8 handling
5259  *
5260  * NOTE: Much of the following code for dealing with unicode is derived from
5261  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
5262  * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
5263  */
5265 /* I've (over)annotated a lot of code snippets because I am not entirely
5266  * confident that the approach taken by this small UTF-8 interface is correct.
5267  * --jonas */
5269 static inline int
5270 unicode_width(unsigned long c)
5272         if (c >= 0x1100 &&
5273            (c <= 0x115f                         /* Hangul Jamo */
5274             || c == 0x2329
5275             || c == 0x232a
5276             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
5277                                                 /* CJK ... Yi */
5278             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
5279             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
5280             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
5281             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
5282             || (c >= 0xffe0  && c <= 0xffe6)
5283             || (c >= 0x20000 && c <= 0x2fffd)
5284             || (c >= 0x30000 && c <= 0x3fffd)))
5285                 return 2;
5287         if (c == '\t')
5288                 return opt_tab_size;
5290         return 1;
5293 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
5294  * Illegal bytes are set one. */
5295 static const unsigned char utf8_bytes[256] = {
5296         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,
5297         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,
5298         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,
5299         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,
5300         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,
5301         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,
5302         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,
5303         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,
5304 };
5306 /* Decode UTF-8 multi-byte representation into a unicode character. */
5307 static inline unsigned long
5308 utf8_to_unicode(const char *string, size_t length)
5310         unsigned long unicode;
5312         switch (length) {
5313         case 1:
5314                 unicode  =   string[0];
5315                 break;
5316         case 2:
5317                 unicode  =  (string[0] & 0x1f) << 6;
5318                 unicode +=  (string[1] & 0x3f);
5319                 break;
5320         case 3:
5321                 unicode  =  (string[0] & 0x0f) << 12;
5322                 unicode += ((string[1] & 0x3f) << 6);
5323                 unicode +=  (string[2] & 0x3f);
5324                 break;
5325         case 4:
5326                 unicode  =  (string[0] & 0x0f) << 18;
5327                 unicode += ((string[1] & 0x3f) << 12);
5328                 unicode += ((string[2] & 0x3f) << 6);
5329                 unicode +=  (string[3] & 0x3f);
5330                 break;
5331         case 5:
5332                 unicode  =  (string[0] & 0x0f) << 24;
5333                 unicode += ((string[1] & 0x3f) << 18);
5334                 unicode += ((string[2] & 0x3f) << 12);
5335                 unicode += ((string[3] & 0x3f) << 6);
5336                 unicode +=  (string[4] & 0x3f);
5337                 break;
5338         case 6:
5339                 unicode  =  (string[0] & 0x01) << 30;
5340                 unicode += ((string[1] & 0x3f) << 24);
5341                 unicode += ((string[2] & 0x3f) << 18);
5342                 unicode += ((string[3] & 0x3f) << 12);
5343                 unicode += ((string[4] & 0x3f) << 6);
5344                 unicode +=  (string[5] & 0x3f);
5345                 break;
5346         default:
5347                 die("Invalid unicode length");
5348         }
5350         /* Invalid characters could return the special 0xfffd value but NUL
5351          * should be just as good. */
5352         return unicode > 0xffff ? 0 : unicode;
5355 /* Calculates how much of string can be shown within the given maximum width
5356  * and sets trimmed parameter to non-zero value if all of string could not be
5357  * shown. If the reserve flag is TRUE, it will reserve at least one
5358  * trailing character, which can be useful when drawing a delimiter.
5359  *
5360  * Returns the number of bytes to output from string to satisfy max_width. */
5361 static size_t
5362 utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve)
5364         const char *start = string;
5365         const char *end = strchr(string, '\0');
5366         unsigned char last_bytes = 0;
5367         size_t last_ucwidth = 0;
5369         *width = 0;
5370         *trimmed = 0;
5372         while (string < end) {
5373                 int c = *(unsigned char *) string;
5374                 unsigned char bytes = utf8_bytes[c];
5375                 size_t ucwidth;
5376                 unsigned long unicode;
5378                 if (string + bytes > end)
5379                         break;
5381                 /* Change representation to figure out whether
5382                  * it is a single- or double-width character. */
5384                 unicode = utf8_to_unicode(string, bytes);
5385                 /* FIXME: Graceful handling of invalid unicode character. */
5386                 if (!unicode)
5387                         break;
5389                 ucwidth = unicode_width(unicode);
5390                 *width  += ucwidth;
5391                 if (*width > max_width) {
5392                         *trimmed = 1;
5393                         *width -= ucwidth;
5394                         if (reserve && *width == max_width) {
5395                                 string -= last_bytes;
5396                                 *width -= last_ucwidth;
5397                         }
5398                         break;
5399                 }
5401                 string  += bytes;
5402                 last_bytes = bytes;
5403                 last_ucwidth = ucwidth;
5404         }
5406         return string - start;
5410 /*
5411  * Status management
5412  */
5414 /* Whether or not the curses interface has been initialized. */
5415 static bool cursed = FALSE;
5417 /* The status window is used for polling keystrokes. */
5418 static WINDOW *status_win;
5420 static bool status_empty = TRUE;
5422 /* Update status and title window. */
5423 static void
5424 report(const char *msg, ...)
5426         struct view *view = display[current_view];
5428         if (input_mode)
5429                 return;
5431         if (!view) {
5432                 char buf[SIZEOF_STR];
5433                 va_list args;
5435                 va_start(args, msg);
5436                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
5437                         buf[sizeof(buf) - 1] = 0;
5438                         buf[sizeof(buf) - 2] = '.';
5439                         buf[sizeof(buf) - 3] = '.';
5440                         buf[sizeof(buf) - 4] = '.';
5441                 }
5442                 va_end(args);
5443                 die("%s", buf);
5444         }
5446         if (!status_empty || *msg) {
5447                 va_list args;
5449                 va_start(args, msg);
5451                 wmove(status_win, 0, 0);
5452                 if (*msg) {
5453                         vwprintw(status_win, msg, args);
5454                         status_empty = FALSE;
5455                 } else {
5456                         status_empty = TRUE;
5457                 }
5458                 wclrtoeol(status_win);
5459                 wrefresh(status_win);
5461                 va_end(args);
5462         }
5464         update_view_title(view);
5465         update_display_cursor(view);
5468 /* Controls when nodelay should be in effect when polling user input. */
5469 static void
5470 set_nonblocking_input(bool loading)
5472         static unsigned int loading_views;
5474         if ((loading == FALSE && loading_views-- == 1) ||
5475             (loading == TRUE  && loading_views++ == 0))
5476                 nodelay(status_win, loading);
5479 static void
5480 init_display(void)
5482         int x, y;
5484         /* Initialize the curses library */
5485         if (isatty(STDIN_FILENO)) {
5486                 cursed = !!initscr();
5487         } else {
5488                 /* Leave stdin and stdout alone when acting as a pager. */
5489                 FILE *io = fopen("/dev/tty", "r+");
5491                 if (!io)
5492                         die("Failed to open /dev/tty");
5493                 cursed = !!newterm(NULL, io, io);
5494         }
5496         if (!cursed)
5497                 die("Failed to initialize curses");
5499         nonl();         /* Tell curses not to do NL->CR/NL on output */
5500         cbreak();       /* Take input chars one at a time, no wait for \n */
5501         noecho();       /* Don't echo input */
5502         leaveok(stdscr, TRUE);
5504         if (has_colors())
5505                 init_colors();
5507         getmaxyx(stdscr, y, x);
5508         status_win = newwin(1, 0, y - 1, 0);
5509         if (!status_win)
5510                 die("Failed to create status window");
5512         /* Enable keyboard mapping */
5513         keypad(status_win, TRUE);
5514         wbkgdset(status_win, get_line_attr(LINE_STATUS));
5516         TABSIZE = opt_tab_size;
5517         if (opt_line_graphics) {
5518                 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
5519         }
5522 static bool
5523 prompt_yesno(const char *prompt)
5525         enum { WAIT, STOP, CANCEL  } status = WAIT;
5526         bool answer = FALSE;
5528         while (status == WAIT) {
5529                 struct view *view;
5530                 int i, key;
5532                 input_mode = TRUE;
5534                 foreach_view (view, i)
5535                         update_view(view);
5537                 input_mode = FALSE;
5539                 mvwprintw(status_win, 0, 0, "%s [Yy]/[Nn]", prompt);
5540                 wclrtoeol(status_win);
5542                 /* Refresh, accept single keystroke of input */
5543                 key = wgetch(status_win);
5544                 switch (key) {
5545                 case ERR:
5546                         break;
5548                 case 'y':
5549                 case 'Y':
5550                         answer = TRUE;
5551                         status = STOP;
5552                         break;
5554                 case KEY_ESC:
5555                 case KEY_RETURN:
5556                 case KEY_ENTER:
5557                 case KEY_BACKSPACE:
5558                 case 'n':
5559                 case 'N':
5560                 case '\n':
5561                 default:
5562                         answer = FALSE;
5563                         status = CANCEL;
5564                 }
5565         }
5567         /* Clear the status window */
5568         status_empty = FALSE;
5569         report("");
5571         return answer;
5574 static char *
5575 read_prompt(const char *prompt)
5577         enum { READING, STOP, CANCEL } status = READING;
5578         static char buf[sizeof(opt_cmd) - STRING_SIZE("git \0")];
5579         int pos = 0;
5581         while (status == READING) {
5582                 struct view *view;
5583                 int i, key;
5585                 input_mode = TRUE;
5587                 foreach_view (view, i)
5588                         update_view(view);
5590                 input_mode = FALSE;
5592                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
5593                 wclrtoeol(status_win);
5595                 /* Refresh, accept single keystroke of input */
5596                 key = wgetch(status_win);
5597                 switch (key) {
5598                 case KEY_RETURN:
5599                 case KEY_ENTER:
5600                 case '\n':
5601                         status = pos ? STOP : CANCEL;
5602                         break;
5604                 case KEY_BACKSPACE:
5605                         if (pos > 0)
5606                                 pos--;
5607                         else
5608                                 status = CANCEL;
5609                         break;
5611                 case KEY_ESC:
5612                         status = CANCEL;
5613                         break;
5615                 case ERR:
5616                         break;
5618                 default:
5619                         if (pos >= sizeof(buf)) {
5620                                 report("Input string too long");
5621                                 return NULL;
5622                         }
5624                         if (isprint(key))
5625                                 buf[pos++] = (char) key;
5626                 }
5627         }
5629         /* Clear the status window */
5630         status_empty = FALSE;
5631         report("");
5633         if (status == CANCEL)
5634                 return NULL;
5636         buf[pos++] = 0;
5638         return buf;
5641 /*
5642  * Repository references
5643  */
5645 static struct ref *refs = NULL;
5646 static size_t refs_alloc = 0;
5647 static size_t refs_size = 0;
5649 /* Id <-> ref store */
5650 static struct ref ***id_refs = NULL;
5651 static size_t id_refs_alloc = 0;
5652 static size_t id_refs_size = 0;
5654 static struct ref **
5655 get_refs(char *id)
5657         struct ref ***tmp_id_refs;
5658         struct ref **ref_list = NULL;
5659         size_t ref_list_alloc = 0;
5660         size_t ref_list_size = 0;
5661         size_t i;
5663         for (i = 0; i < id_refs_size; i++)
5664                 if (!strcmp(id, id_refs[i][0]->id))
5665                         return id_refs[i];
5667         tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
5668                                     sizeof(*id_refs));
5669         if (!tmp_id_refs)
5670                 return NULL;
5672         id_refs = tmp_id_refs;
5674         for (i = 0; i < refs_size; i++) {
5675                 struct ref **tmp;
5677                 if (strcmp(id, refs[i].id))
5678                         continue;
5680                 tmp = realloc_items(ref_list, &ref_list_alloc,
5681                                     ref_list_size + 1, sizeof(*ref_list));
5682                 if (!tmp) {
5683                         if (ref_list)
5684                                 free(ref_list);
5685                         return NULL;
5686                 }
5688                 ref_list = tmp;
5689                 if (ref_list_size > 0)
5690                         ref_list[ref_list_size - 1]->next = 1;
5691                 ref_list[ref_list_size] = &refs[i];
5693                 /* XXX: The properties of the commit chains ensures that we can
5694                  * safely modify the shared ref. The repo references will
5695                  * always be similar for the same id. */
5696                 ref_list[ref_list_size]->next = 0;
5697                 ref_list_size++;
5698         }
5700         if (ref_list)
5701                 id_refs[id_refs_size++] = ref_list;
5703         return ref_list;
5706 static int
5707 read_ref(char *id, size_t idlen, char *name, size_t namelen)
5709         struct ref *ref;
5710         bool tag = FALSE;
5711         bool ltag = FALSE;
5712         bool remote = FALSE;
5713         bool tracked = FALSE;
5714         bool check_replace = FALSE;
5715         bool head = FALSE;
5717         if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
5718                 if (!strcmp(name + namelen - 3, "^{}")) {
5719                         namelen -= 3;
5720                         name[namelen] = 0;
5721                         if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
5722                                 check_replace = TRUE;
5723                 } else {
5724                         ltag = TRUE;
5725                 }
5727                 tag = TRUE;
5728                 namelen -= STRING_SIZE("refs/tags/");
5729                 name    += STRING_SIZE("refs/tags/");
5731         } else if (!strncmp(name, "refs/remotes/", STRING_SIZE("refs/remotes/"))) {
5732                 remote = TRUE;
5733                 namelen -= STRING_SIZE("refs/remotes/");
5734                 name    += STRING_SIZE("refs/remotes/");
5735                 tracked  = !strcmp(opt_remote, name);
5737         } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
5738                 namelen -= STRING_SIZE("refs/heads/");
5739                 name    += STRING_SIZE("refs/heads/");
5740                 head     = !strncmp(opt_head, name, namelen);
5742         } else if (!strcmp(name, "HEAD")) {
5743                 opt_no_head = FALSE;
5744                 return OK;
5745         }
5747         if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
5748                 /* it's an annotated tag, replace the previous sha1 with the
5749                  * resolved commit id; relies on the fact git-ls-remote lists
5750                  * the commit id of an annotated tag right beofre the commit id
5751                  * it points to. */
5752                 refs[refs_size - 1].ltag = ltag;
5753                 string_copy_rev(refs[refs_size - 1].id, id);
5755                 return OK;
5756         }
5757         refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
5758         if (!refs)
5759                 return ERR;
5761         ref = &refs[refs_size++];
5762         ref->name = malloc(namelen + 1);
5763         if (!ref->name)
5764                 return ERR;
5766         strncpy(ref->name, name, namelen);
5767         ref->name[namelen] = 0;
5768         ref->head = head;
5769         ref->tag = tag;
5770         ref->ltag = ltag;
5771         ref->remote = remote;
5772         ref->tracked = tracked;
5773         string_copy_rev(ref->id, id);
5775         return OK;
5778 static int
5779 load_refs(void)
5781         const char *cmd_env = getenv("TIG_LS_REMOTE");
5782         const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
5784         return read_properties(popen(cmd, "r"), "\t", read_ref);
5787 static int
5788 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
5790         if (!strcmp(name, "i18n.commitencoding"))
5791                 string_ncopy(opt_encoding, value, valuelen);
5793         if (!strcmp(name, "core.editor"))
5794                 string_ncopy(opt_editor, value, valuelen);
5796         /* branch.<head>.remote */
5797         if (*opt_head &&
5798             !strncmp(name, "branch.", 7) &&
5799             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
5800             !strcmp(name + 7 + strlen(opt_head), ".remote"))
5801                 string_ncopy(opt_remote, value, valuelen);
5803         if (*opt_head && *opt_remote &&
5804             !strncmp(name, "branch.", 7) &&
5805             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
5806             !strcmp(name + 7 + strlen(opt_head), ".merge")) {
5807                 size_t from = strlen(opt_remote);
5809                 if (!strncmp(value, "refs/heads/", STRING_SIZE("refs/heads/"))) {
5810                         value += STRING_SIZE("refs/heads/");
5811                         valuelen -= STRING_SIZE("refs/heads/");
5812                 }
5814                 if (!string_format_from(opt_remote, &from, "/%s", value))
5815                         opt_remote[0] = 0;
5816         }
5818         return OK;
5821 static int
5822 load_git_config(void)
5824         return read_properties(popen(GIT_CONFIG " --list", "r"),
5825                                "=", read_repo_config_option);
5828 static int
5829 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
5831         if (!opt_git_dir[0]) {
5832                 string_ncopy(opt_git_dir, name, namelen);
5834         } else if (opt_is_inside_work_tree == -1) {
5835                 /* This can be 3 different values depending on the
5836                  * version of git being used. If git-rev-parse does not
5837                  * understand --is-inside-work-tree it will simply echo
5838                  * the option else either "true" or "false" is printed.
5839                  * Default to true for the unknown case. */
5840                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
5842         } else if (opt_cdup[0] == ' ') {
5843                 string_ncopy(opt_cdup, name, namelen);
5844         } else {
5845                 if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
5846                         namelen -= STRING_SIZE("refs/heads/");
5847                         name    += STRING_SIZE("refs/heads/");
5848                         string_ncopy(opt_head, name, namelen);
5849                 }
5850         }
5852         return OK;
5855 static int
5856 load_repo_info(void)
5858         int result;
5859         FILE *pipe = popen("(git rev-parse --git-dir --is-inside-work-tree "
5860                            " --show-cdup; git symbolic-ref HEAD) 2>/dev/null", "r");
5862         /* XXX: The line outputted by "--show-cdup" can be empty so
5863          * initialize it to something invalid to make it possible to
5864          * detect whether it has been set or not. */
5865         opt_cdup[0] = ' ';
5867         result = read_properties(pipe, "=", read_repo_info);
5868         if (opt_cdup[0] == ' ')
5869                 opt_cdup[0] = 0;
5871         return result;
5874 static int
5875 read_properties(FILE *pipe, const char *separators,
5876                 int (*read_property)(char *, size_t, char *, size_t))
5878         char buffer[BUFSIZ];
5879         char *name;
5880         int state = OK;
5882         if (!pipe)
5883                 return ERR;
5885         while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
5886                 char *value;
5887                 size_t namelen;
5888                 size_t valuelen;
5890                 name = chomp_string(name);
5891                 namelen = strcspn(name, separators);
5893                 if (name[namelen]) {
5894                         name[namelen] = 0;
5895                         value = chomp_string(name + namelen + 1);
5896                         valuelen = strlen(value);
5898                 } else {
5899                         value = "";
5900                         valuelen = 0;
5901                 }
5903                 state = read_property(name, namelen, value, valuelen);
5904         }
5906         if (state != ERR && ferror(pipe))
5907                 state = ERR;
5909         pclose(pipe);
5911         return state;
5915 /*
5916  * Main
5917  */
5919 static void __NORETURN
5920 quit(int sig)
5922         /* XXX: Restore tty modes and let the OS cleanup the rest! */
5923         if (cursed)
5924                 endwin();
5925         exit(0);
5928 static void __NORETURN
5929 die(const char *err, ...)
5931         va_list args;
5933         endwin();
5935         va_start(args, err);
5936         fputs("tig: ", stderr);
5937         vfprintf(stderr, err, args);
5938         fputs("\n", stderr);
5939         va_end(args);
5941         exit(1);
5944 static void
5945 warn(const char *msg, ...)
5947         va_list args;
5949         va_start(args, msg);
5950         fputs("tig warning: ", stderr);
5951         vfprintf(stderr, msg, args);
5952         fputs("\n", stderr);
5953         va_end(args);
5956 int
5957 main(int argc, char *argv[])
5959         struct view *view;
5960         enum request request;
5961         size_t i;
5963         signal(SIGINT, quit);
5965         if (setlocale(LC_ALL, "")) {
5966                 char *codeset = nl_langinfo(CODESET);
5968                 string_ncopy(opt_codeset, codeset, strlen(codeset));
5969         }
5971         if (load_repo_info() == ERR)
5972                 die("Failed to load repo info.");
5974         if (load_options() == ERR)
5975                 die("Failed to load user config.");
5977         if (load_git_config() == ERR)
5978                 die("Failed to load repo config.");
5980         request = parse_options(argc, argv);
5981         if (request == REQ_NONE)
5982                 return 0;
5984         /* Require a git repository unless when running in pager mode. */
5985         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
5986                 die("Not a git repository");
5988         if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
5989                 opt_utf8 = FALSE;
5991         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
5992                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
5993                 if (opt_iconv == ICONV_NONE)
5994                         die("Failed to initialize character set conversion");
5995         }
5997         if (*opt_git_dir && load_refs() == ERR)
5998                 die("Failed to load refs.");
6000         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
6001                 view->cmd_env = getenv(view->cmd_env);
6003         init_display();
6005         while (view_driver(display[current_view], request)) {
6006                 int key;
6007                 int i;
6009                 foreach_view (view, i)
6010                         update_view(view);
6012                 /* Refresh, accept single keystroke of input */
6013                 key = wgetch(status_win);
6015                 /* wgetch() with nodelay() enabled returns ERR when there's no
6016                  * input. */
6017                 if (key == ERR) {
6018                         request = REQ_NONE;
6019                         continue;
6020                 }
6022                 request = get_keybinding(display[current_view]->keymap, key);
6024                 /* Some low-level request handling. This keeps access to
6025                  * status_win restricted. */
6026                 switch (request) {
6027                 case REQ_PROMPT:
6028                 {
6029                         char *cmd = read_prompt(":");
6031                         if (cmd && string_format(opt_cmd, "git %s", cmd)) {
6032                                 if (strncmp(cmd, "show", 4) && isspace(cmd[4])) {
6033                                         request = REQ_VIEW_DIFF;
6034                                 } else {
6035                                         request = REQ_VIEW_PAGER;
6036                                 }
6038                                 /* Always reload^Wrerun commands from the prompt. */
6039                                 open_view(view, request, OPEN_RELOAD);
6040                         }
6042                         request = REQ_NONE;
6043                         break;
6044                 }
6045                 case REQ_SEARCH:
6046                 case REQ_SEARCH_BACK:
6047                 {
6048                         const char *prompt = request == REQ_SEARCH
6049                                            ? "/" : "?";
6050                         char *search = read_prompt(prompt);
6052                         if (search)
6053                                 string_ncopy(opt_search, search, strlen(search));
6054                         else
6055                                 request = REQ_NONE;
6056                         break;
6057                 }
6058                 case REQ_SCREEN_RESIZE:
6059                 {
6060                         int height, width;
6062                         getmaxyx(stdscr, height, width);
6064                         /* Resize the status view and let the view driver take
6065                          * care of resizing the displayed views. */
6066                         wresize(status_win, 1, width);
6067                         mvwin(status_win, height - 1, 0);
6068                         wrefresh(status_win);
6069                         break;
6070                 }
6071                 default:
6072                         break;
6073                 }
6074         }
6076         quit(0);
6078         return 0;