Code

Collect remaining string in last entry when parsing config file lines
[tig.git] / tig.c
1 /* Copyright (c) 2006-2007 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 #include <curses.h>
47 #if __GNUC__ >= 3
48 #define __NORETURN __attribute__((__noreturn__))
49 #else
50 #define __NORETURN
51 #endif
53 static void __NORETURN die(const char *err, ...);
54 static void report(const char *msg, ...);
55 static int read_properties(FILE *pipe, const char *separators, int (*read)(char *, size_t, char *, size_t));
56 static void set_nonblocking_input(bool loading);
57 static size_t utf8_length(const char *string, size_t max_width, int *coloffset, int *trimmed);
59 #define ABS(x)          ((x) >= 0  ? (x) : -(x))
60 #define MIN(x, y)       ((x) < (y) ? (x) :  (y))
62 #define ARRAY_SIZE(x)   (sizeof(x) / sizeof(x[0]))
63 #define STRING_SIZE(x)  (sizeof(x) - 1)
65 #define SIZEOF_STR      1024    /* Default string size. */
66 #define SIZEOF_REF      256     /* Size of symbolic or SHA1 ID. */
67 #define SIZEOF_REV      41      /* Holds a SHA-1 and an ending NUL */
69 /* Revision graph */
71 #define REVGRAPH_INIT   'I'
72 #define REVGRAPH_MERGE  'M'
73 #define REVGRAPH_BRANCH '+'
74 #define REVGRAPH_COMMIT '*'
75 #define REVGRAPH_LINE   '|'
77 #define SIZEOF_REVGRAPH 19      /* Size of revision ancestry graphics. */
79 /* This color name can be used to refer to the default term colors. */
80 #define COLOR_DEFAULT   (-1)
82 #define ICONV_NONE      ((iconv_t) -1)
83 #ifndef ICONV_CONST
84 #define ICONV_CONST     /* nothing */
85 #endif
87 /* The format and size of the date column in the main view. */
88 #define DATE_FORMAT     "%Y-%m-%d %H:%M"
89 #define DATE_COLS       STRING_SIZE("2006-04-29 14:21 ")
91 #define AUTHOR_COLS     20
93 /* The default interval between line numbers. */
94 #define NUMBER_INTERVAL 1
96 #define TABSIZE         8
98 #define SCALE_SPLIT_VIEW(height)        ((height) * 2 / 3)
100 #ifndef GIT_CONFIG
101 #define GIT_CONFIG "git config"
102 #endif
104 #define TIG_LS_REMOTE \
105         "git ls-remote $(git rev-parse --git-dir) 2>/dev/null"
107 #define TIG_DIFF_CMD \
108         "git show --root --patch-with-stat --find-copies-harder -B -C %s 2>/dev/null"
110 #define TIG_LOG_CMD     \
111         "git log --cc --stat -n100 %s 2>/dev/null"
113 #define TIG_MAIN_CMD \
114         "git log --topo-order --pretty=raw %s 2>/dev/null"
116 #define TIG_TREE_CMD    \
117         "git ls-tree %s %s"
119 #define TIG_BLOB_CMD    \
120         "git cat-file blob %s"
122 /* XXX: Needs to be defined to the empty string. */
123 #define TIG_HELP_CMD    ""
124 #define TIG_PAGER_CMD   ""
125 #define TIG_STATUS_CMD  ""
126 #define TIG_STAGE_CMD   ""
128 /* Some ascii-shorthands fitted into the ncurses namespace. */
129 #define KEY_TAB         '\t'
130 #define KEY_RETURN      '\r'
131 #define KEY_ESC         27
134 struct ref {
135         char *name;             /* Ref name; tag or head names are shortened. */
136         char id[SIZEOF_REV];    /* Commit SHA1 ID */
137         unsigned int tag:1;     /* Is it a tag? */
138         unsigned int remote:1;  /* Is it a remote ref? */
139         unsigned int next:1;    /* For ref lists: are there more refs? */
140 };
142 static struct ref **get_refs(char *id);
144 struct int_map {
145         const char *name;
146         int namelen;
147         int value;
148 };
150 static int
151 set_from_int_map(struct int_map *map, size_t map_size,
152                  int *value, const char *name, int namelen)
155         int i;
157         for (i = 0; i < map_size; i++)
158                 if (namelen == map[i].namelen &&
159                     !strncasecmp(name, map[i].name, namelen)) {
160                         *value = map[i].value;
161                         return OK;
162                 }
164         return ERR;
168 /*
169  * String helpers
170  */
172 static inline void
173 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
175         if (srclen > dstlen - 1)
176                 srclen = dstlen - 1;
178         strncpy(dst, src, srclen);
179         dst[srclen] = 0;
182 /* Shorthands for safely copying into a fixed buffer. */
184 #define string_copy(dst, src) \
185         string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
187 #define string_ncopy(dst, src, srclen) \
188         string_ncopy_do(dst, sizeof(dst), src, srclen)
190 #define string_copy_rev(dst, src) \
191         string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
193 #define string_add(dst, from, src) \
194         string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
196 static char *
197 chomp_string(char *name)
199         int namelen;
201         while (isspace(*name))
202                 name++;
204         namelen = strlen(name) - 1;
205         while (namelen > 0 && isspace(name[namelen]))
206                 name[namelen--] = 0;
208         return name;
211 static bool
212 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
214         va_list args;
215         size_t pos = bufpos ? *bufpos : 0;
217         va_start(args, fmt);
218         pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
219         va_end(args);
221         if (bufpos)
222                 *bufpos = pos;
224         return pos >= bufsize ? FALSE : TRUE;
227 #define string_format(buf, fmt, args...) \
228         string_nformat(buf, sizeof(buf), NULL, fmt, args)
230 #define string_format_from(buf, from, fmt, args...) \
231         string_nformat(buf, sizeof(buf), from, fmt, args)
233 static int
234 string_enum_compare(const char *str1, const char *str2, int len)
236         size_t i;
238 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
240         /* Diff-Header == DIFF_HEADER */
241         for (i = 0; i < len; i++) {
242                 if (toupper(str1[i]) == toupper(str2[i]))
243                         continue;
245                 if (string_enum_sep(str1[i]) &&
246                     string_enum_sep(str2[i]))
247                         continue;
249                 return str1[i] - str2[i];
250         }
252         return 0;
255 /* Shell quoting
256  *
257  * NOTE: The following is a slightly modified copy of the git project's shell
258  * quoting routines found in the quote.c file.
259  *
260  * Help to copy the thing properly quoted for the shell safety.  any single
261  * quote is replaced with '\'', any exclamation point is replaced with '\!',
262  * and the whole thing is enclosed in a
263  *
264  * E.g.
265  *  original     sq_quote     result
266  *  name     ==> name      ==> 'name'
267  *  a b      ==> a b       ==> 'a b'
268  *  a'b      ==> a'\''b    ==> 'a'\''b'
269  *  a!b      ==> a'\!'b    ==> 'a'\!'b'
270  */
272 static size_t
273 sq_quote(char buf[SIZEOF_STR], size_t bufsize, const char *src)
275         char c;
277 #define BUFPUT(x) do { if (bufsize < SIZEOF_STR) buf[bufsize++] = (x); } while (0)
279         BUFPUT('\'');
280         while ((c = *src++)) {
281                 if (c == '\'' || c == '!') {
282                         BUFPUT('\'');
283                         BUFPUT('\\');
284                         BUFPUT(c);
285                         BUFPUT('\'');
286                 } else {
287                         BUFPUT(c);
288                 }
289         }
290         BUFPUT('\'');
292         if (bufsize < SIZEOF_STR)
293                 buf[bufsize] = 0;
295         return bufsize;
299 /*
300  * User requests
301  */
303 #define REQ_INFO \
304         /* XXX: Keep the view request first and in sync with views[]. */ \
305         REQ_GROUP("View switching") \
306         REQ_(VIEW_MAIN,         "Show main view"), \
307         REQ_(VIEW_DIFF,         "Show diff view"), \
308         REQ_(VIEW_LOG,          "Show log view"), \
309         REQ_(VIEW_TREE,         "Show tree view"), \
310         REQ_(VIEW_BLOB,         "Show blob view"), \
311         REQ_(VIEW_HELP,         "Show help page"), \
312         REQ_(VIEW_PAGER,        "Show pager view"), \
313         REQ_(VIEW_STATUS,       "Show status view"), \
314         REQ_(VIEW_STAGE,        "Show stage view"), \
315         \
316         REQ_GROUP("View manipulation") \
317         REQ_(ENTER,             "Enter current line and scroll"), \
318         REQ_(NEXT,              "Move to next"), \
319         REQ_(PREVIOUS,          "Move to previous"), \
320         REQ_(VIEW_NEXT,         "Move focus to next view"), \
321         REQ_(REFRESH,           "Reload and refresh"), \
322         REQ_(VIEW_CLOSE,        "Close the current view"), \
323         REQ_(QUIT,              "Close all views and quit"), \
324         \
325         REQ_GROUP("Cursor navigation") \
326         REQ_(MOVE_UP,           "Move cursor one line up"), \
327         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
328         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
329         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
330         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
331         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
332         \
333         REQ_GROUP("Scrolling") \
334         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
335         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
336         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
337         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
338         \
339         REQ_GROUP("Searching") \
340         REQ_(SEARCH,            "Search the view"), \
341         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
342         REQ_(FIND_NEXT,         "Find next search match"), \
343         REQ_(FIND_PREV,         "Find previous search match"), \
344         \
345         REQ_GROUP("Misc") \
346         REQ_(PROMPT,            "Bring up the prompt"), \
347         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
348         REQ_(SCREEN_RESIZE,     "Resize the screen"), \
349         REQ_(SHOW_VERSION,      "Show version information"), \
350         REQ_(STOP_LOADING,      "Stop all loading views"), \
351         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
352         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
353         REQ_(STATUS_UPDATE,     "Update file status"), \
354         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
355         REQ_(EDIT,              "Open in editor"), \
356         REQ_(NONE,              "Do nothing")
359 /* User action requests. */
360 enum request {
361 #define REQ_GROUP(help)
362 #define REQ_(req, help) REQ_##req
364         /* Offset all requests to avoid conflicts with ncurses getch values. */
365         REQ_OFFSET = KEY_MAX + 1,
366         REQ_INFO
368 #undef  REQ_GROUP
369 #undef  REQ_
370 };
372 struct request_info {
373         enum request request;
374         char *name;
375         int namelen;
376         char *help;
377 };
379 static struct request_info req_info[] = {
380 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
381 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
382         REQ_INFO
383 #undef  REQ_GROUP
384 #undef  REQ_
385 };
387 static enum request
388 get_request(const char *name)
390         int namelen = strlen(name);
391         int i;
393         for (i = 0; i < ARRAY_SIZE(req_info); i++)
394                 if (req_info[i].namelen == namelen &&
395                     !string_enum_compare(req_info[i].name, name, namelen))
396                         return req_info[i].request;
398         return REQ_NONE;
402 /*
403  * Options
404  */
406 static const char usage[] =
407 "tig " TIG_VERSION " (" __DATE__ ")\n"
408 "\n"
409 "Usage: tig [options]\n"
410 "   or: tig [options] [--] [git log options]\n"
411 "   or: tig [options] log  [git log options]\n"
412 "   or: tig [options] diff [git diff options]\n"
413 "   or: tig [options] show [git show options]\n"
414 "   or: tig [options] <    [git command output]\n"
415 "\n"
416 "Options:\n"
417 "  -l                          Start up in log view\n"
418 "  -d                          Start up in diff view\n"
419 "  -S                          Start up in status view\n"
420 "  -n[I], --line-number[=I]    Show line numbers with given interval\n"
421 "  -b[N], --tab-size[=N]       Set number of spaces for tab expansion\n"
422 "  --                          Mark end of tig options\n"
423 "  -v, --version               Show version and exit\n"
424 "  -h, --help                  Show help message and exit\n";
426 /* Option and state variables. */
427 static bool opt_line_number             = FALSE;
428 static bool opt_rev_graph               = FALSE;
429 static int opt_num_interval             = NUMBER_INTERVAL;
430 static int opt_tab_size                 = TABSIZE;
431 static enum request opt_request         = REQ_VIEW_MAIN;
432 static char opt_cmd[SIZEOF_STR]         = "";
433 static char opt_path[SIZEOF_STR]        = "";
434 static FILE *opt_pipe                   = NULL;
435 static char opt_encoding[20]            = "UTF-8";
436 static bool opt_utf8                    = TRUE;
437 static char opt_codeset[20]             = "UTF-8";
438 static iconv_t opt_iconv                = ICONV_NONE;
439 static char opt_search[SIZEOF_STR]      = "";
440 static char opt_cdup[SIZEOF_STR]        = "";
441 static char opt_git_dir[SIZEOF_STR]     = "";
442 static char opt_is_inside_work_tree     = -1; /* set to TRUE or FALSE */
443 static char opt_editor[SIZEOF_STR]      = "";
445 enum option_type {
446         OPT_NONE,
447         OPT_INT,
448 };
450 static bool
451 check_option(char *opt, char short_name, char *name, enum option_type type, ...)
453         va_list args;
454         char *value = "";
455         int *number;
457         if (opt[0] != '-')
458                 return FALSE;
460         if (opt[1] == '-') {
461                 int namelen = strlen(name);
463                 opt += 2;
465                 if (strncmp(opt, name, namelen))
466                         return FALSE;
468                 if (opt[namelen] == '=')
469                         value = opt + namelen + 1;
471         } else {
472                 if (!short_name || opt[1] != short_name)
473                         return FALSE;
474                 value = opt + 2;
475         }
477         va_start(args, type);
478         if (type == OPT_INT) {
479                 number = va_arg(args, int *);
480                 if (isdigit(*value))
481                         *number = atoi(value);
482         }
483         va_end(args);
485         return TRUE;
488 /* Returns the index of log or diff command or -1 to exit. */
489 static bool
490 parse_options(int argc, char *argv[])
492         int i;
494         for (i = 1; i < argc; i++) {
495                 char *opt = argv[i];
497                 if (!strcmp(opt, "log") ||
498                     !strcmp(opt, "diff") ||
499                     !strcmp(opt, "show")) {
500                         opt_request = opt[0] == 'l'
501                                     ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
502                         break;
503                 }
505                 if (opt[0] && opt[0] != '-')
506                         break;
508                 if (!strcmp(opt, "-l")) {
509                         opt_request = REQ_VIEW_LOG;
510                         continue;
511                 }
513                 if (!strcmp(opt, "-d")) {
514                         opt_request = REQ_VIEW_DIFF;
515                         continue;
516                 }
518                 if (!strcmp(opt, "-S")) {
519                         opt_request = REQ_VIEW_STATUS;
520                         continue;
521                 }
523                 if (check_option(opt, 'n', "line-number", OPT_INT, &opt_num_interval)) {
524                         opt_line_number = TRUE;
525                         continue;
526                 }
528                 if (check_option(opt, 'b', "tab-size", OPT_INT, &opt_tab_size)) {
529                         opt_tab_size = MIN(opt_tab_size, TABSIZE);
530                         continue;
531                 }
533                 if (check_option(opt, 'v', "version", OPT_NONE)) {
534                         printf("tig version %s\n", TIG_VERSION);
535                         return FALSE;
536                 }
538                 if (check_option(opt, 'h', "help", OPT_NONE)) {
539                         printf(usage);
540                         return FALSE;
541                 }
543                 if (!strcmp(opt, "--")) {
544                         i++;
545                         break;
546                 }
548                 die("unknown option '%s'\n\n%s", opt, usage);
549         }
551         if (!isatty(STDIN_FILENO)) {
552                 opt_request = REQ_VIEW_PAGER;
553                 opt_pipe = stdin;
555         } else if (i < argc) {
556                 size_t buf_size;
558                 if (opt_request == REQ_VIEW_MAIN)
559                         /* XXX: This is vulnerable to the user overriding
560                          * options required for the main view parser. */
561                         string_copy(opt_cmd, "git log --pretty=raw");
562                 else
563                         string_copy(opt_cmd, "git");
564                 buf_size = strlen(opt_cmd);
566                 while (buf_size < sizeof(opt_cmd) && i < argc) {
567                         opt_cmd[buf_size++] = ' ';
568                         buf_size = sq_quote(opt_cmd, buf_size, argv[i++]);
569                 }
571                 if (buf_size >= sizeof(opt_cmd))
572                         die("command too long");
574                 opt_cmd[buf_size] = 0;
575         }
577         if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
578                 opt_utf8 = FALSE;
580         return TRUE;
584 /*
585  * Line-oriented content detection.
586  */
588 #define LINE_INFO \
589 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
590 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
591 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
592 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
593 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
594 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
595 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
596 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
597 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
598 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
599 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
600 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
601 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
602 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
603 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
604 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
605 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
606 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
607 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
608 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
609 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
610 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
611 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
612 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
613 LINE(AUTHOR,       "author ",           COLOR_CYAN,     COLOR_DEFAULT,  0), \
614 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
615 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
616 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
617 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
618 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
619 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
620 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
621 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
622 LINE(MAIN_DATE,    "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
623 LINE(MAIN_AUTHOR,  "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
624 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
625 LINE(MAIN_DELIM,   "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
626 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
627 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
628 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
629 LINE(TREE_DIR,     "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
630 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
631 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
632 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
633 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
634 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
635 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0)
637 enum line_type {
638 #define LINE(type, line, fg, bg, attr) \
639         LINE_##type
640         LINE_INFO
641 #undef  LINE
642 };
644 struct line_info {
645         const char *name;       /* Option name. */
646         int namelen;            /* Size of option name. */
647         const char *line;       /* The start of line to match. */
648         int linelen;            /* Size of string to match. */
649         int fg, bg, attr;       /* Color and text attributes for the lines. */
650 };
652 static struct line_info line_info[] = {
653 #define LINE(type, line, fg, bg, attr) \
654         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
655         LINE_INFO
656 #undef  LINE
657 };
659 static enum line_type
660 get_line_type(char *line)
662         int linelen = strlen(line);
663         enum line_type type;
665         for (type = 0; type < ARRAY_SIZE(line_info); type++)
666                 /* Case insensitive search matches Signed-off-by lines better. */
667                 if (linelen >= line_info[type].linelen &&
668                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
669                         return type;
671         return LINE_DEFAULT;
674 static inline int
675 get_line_attr(enum line_type type)
677         assert(type < ARRAY_SIZE(line_info));
678         return COLOR_PAIR(type) | line_info[type].attr;
681 static struct line_info *
682 get_line_info(char *name, int namelen)
684         enum line_type type;
686         for (type = 0; type < ARRAY_SIZE(line_info); type++)
687                 if (namelen == line_info[type].namelen &&
688                     !string_enum_compare(line_info[type].name, name, namelen))
689                         return &line_info[type];
691         return NULL;
694 static void
695 init_colors(void)
697         int default_bg = COLOR_BLACK;
698         int default_fg = COLOR_WHITE;
699         enum line_type type;
701         start_color();
703         if (use_default_colors() != ERR) {
704                 default_bg = -1;
705                 default_fg = -1;
706         }
708         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
709                 struct line_info *info = &line_info[type];
710                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
711                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
713                 init_pair(type, fg, bg);
714         }
717 struct line {
718         enum line_type type;
720         /* State flags */
721         unsigned int selected:1;
723         void *data;             /* User data */
724 };
727 /*
728  * Keys
729  */
731 struct keybinding {
732         int alias;
733         enum request request;
734         struct keybinding *next;
735 };
737 static struct keybinding default_keybindings[] = {
738         /* View switching */
739         { 'm',          REQ_VIEW_MAIN },
740         { 'd',          REQ_VIEW_DIFF },
741         { 'l',          REQ_VIEW_LOG },
742         { 't',          REQ_VIEW_TREE },
743         { 'f',          REQ_VIEW_BLOB },
744         { 'p',          REQ_VIEW_PAGER },
745         { 'h',          REQ_VIEW_HELP },
746         { 'S',          REQ_VIEW_STATUS },
747         { 'c',          REQ_VIEW_STAGE },
749         /* View manipulation */
750         { 'q',          REQ_VIEW_CLOSE },
751         { KEY_TAB,      REQ_VIEW_NEXT },
752         { KEY_RETURN,   REQ_ENTER },
753         { KEY_UP,       REQ_PREVIOUS },
754         { KEY_DOWN,     REQ_NEXT },
755         { 'R',          REQ_REFRESH },
757         /* Cursor navigation */
758         { 'k',          REQ_MOVE_UP },
759         { 'j',          REQ_MOVE_DOWN },
760         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
761         { KEY_END,      REQ_MOVE_LAST_LINE },
762         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
763         { ' ',          REQ_MOVE_PAGE_DOWN },
764         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
765         { 'b',          REQ_MOVE_PAGE_UP },
766         { '-',          REQ_MOVE_PAGE_UP },
768         /* Scrolling */
769         { KEY_IC,       REQ_SCROLL_LINE_UP },
770         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
771         { 'w',          REQ_SCROLL_PAGE_UP },
772         { 's',          REQ_SCROLL_PAGE_DOWN },
774         /* Searching */
775         { '/',          REQ_SEARCH },
776         { '?',          REQ_SEARCH_BACK },
777         { 'n',          REQ_FIND_NEXT },
778         { 'N',          REQ_FIND_PREV },
780         /* Misc */
781         { 'Q',          REQ_QUIT },
782         { 'z',          REQ_STOP_LOADING },
783         { 'v',          REQ_SHOW_VERSION },
784         { 'r',          REQ_SCREEN_REDRAW },
785         { '.',          REQ_TOGGLE_LINENO },
786         { 'g',          REQ_TOGGLE_REV_GRAPH },
787         { ':',          REQ_PROMPT },
788         { 'u',          REQ_STATUS_UPDATE },
789         { 'M',          REQ_STATUS_MERGE },
790         { 'e',          REQ_EDIT },
792         /* Using the ncurses SIGWINCH handler. */
793         { KEY_RESIZE,   REQ_SCREEN_RESIZE },
794 };
796 #define KEYMAP_INFO \
797         KEYMAP_(GENERIC), \
798         KEYMAP_(MAIN), \
799         KEYMAP_(DIFF), \
800         KEYMAP_(LOG), \
801         KEYMAP_(TREE), \
802         KEYMAP_(BLOB), \
803         KEYMAP_(PAGER), \
804         KEYMAP_(HELP), \
805         KEYMAP_(STATUS), \
806         KEYMAP_(STAGE)
808 enum keymap {
809 #define KEYMAP_(name) KEYMAP_##name
810         KEYMAP_INFO
811 #undef  KEYMAP_
812 };
814 static struct int_map keymap_table[] = {
815 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
816         KEYMAP_INFO
817 #undef  KEYMAP_
818 };
820 #define set_keymap(map, name) \
821         set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
823 static struct keybinding *keybindings[ARRAY_SIZE(keymap_table)];
825 static void
826 add_keybinding(enum keymap keymap, enum request request, int key)
828         struct keybinding *keybinding;
830         keybinding = calloc(1, sizeof(*keybinding));
831         if (!keybinding)
832                 die("Failed to allocate keybinding");
834         keybinding->alias = key;
835         keybinding->request = request;
836         keybinding->next = keybindings[keymap];
837         keybindings[keymap] = keybinding;
840 /* Looks for a key binding first in the given map, then in the generic map, and
841  * lastly in the default keybindings. */
842 static enum request
843 get_keybinding(enum keymap keymap, int key)
845         struct keybinding *kbd;
846         int i;
848         for (kbd = keybindings[keymap]; kbd; kbd = kbd->next)
849                 if (kbd->alias == key)
850                         return kbd->request;
852         for (kbd = keybindings[KEYMAP_GENERIC]; kbd; kbd = kbd->next)
853                 if (kbd->alias == key)
854                         return kbd->request;
856         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
857                 if (default_keybindings[i].alias == key)
858                         return default_keybindings[i].request;
860         return (enum request) key;
864 struct key {
865         char *name;
866         int value;
867 };
869 static struct key key_table[] = {
870         { "Enter",      KEY_RETURN },
871         { "Space",      ' ' },
872         { "Backspace",  KEY_BACKSPACE },
873         { "Tab",        KEY_TAB },
874         { "Escape",     KEY_ESC },
875         { "Left",       KEY_LEFT },
876         { "Right",      KEY_RIGHT },
877         { "Up",         KEY_UP },
878         { "Down",       KEY_DOWN },
879         { "Insert",     KEY_IC },
880         { "Delete",     KEY_DC },
881         { "Hash",       '#' },
882         { "Home",       KEY_HOME },
883         { "End",        KEY_END },
884         { "PageUp",     KEY_PPAGE },
885         { "PageDown",   KEY_NPAGE },
886         { "F1",         KEY_F(1) },
887         { "F2",         KEY_F(2) },
888         { "F3",         KEY_F(3) },
889         { "F4",         KEY_F(4) },
890         { "F5",         KEY_F(5) },
891         { "F6",         KEY_F(6) },
892         { "F7",         KEY_F(7) },
893         { "F8",         KEY_F(8) },
894         { "F9",         KEY_F(9) },
895         { "F10",        KEY_F(10) },
896         { "F11",        KEY_F(11) },
897         { "F12",        KEY_F(12) },
898 };
900 static int
901 get_key_value(const char *name)
903         int i;
905         for (i = 0; i < ARRAY_SIZE(key_table); i++)
906                 if (!strcasecmp(key_table[i].name, name))
907                         return key_table[i].value;
909         if (strlen(name) == 1 && isprint(*name))
910                 return (int) *name;
912         return ERR;
915 static char *
916 get_key_name(int key_value)
918         static char key_char[] = "'X'";
919         char *seq = NULL;
920         int key;
922         for (key = 0; key < ARRAY_SIZE(key_table); key++)
923                 if (key_table[key].value == key_value)
924                         seq = key_table[key].name;
926         if (seq == NULL &&
927             key_value < 127 &&
928             isprint(key_value)) {
929                 key_char[1] = (char) key_value;
930                 seq = key_char;
931         }
933         return seq ? seq : "'?'";
936 static char *
937 get_key(enum request request)
939         static char buf[BUFSIZ];
940         static char key_char[] = "'X'";
941         size_t pos = 0;
942         char *sep = "";
943         int i;
945         buf[pos] = 0;
947         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
948                 struct keybinding *keybinding = &default_keybindings[i];
949                 char *seq = NULL;
950                 int key;
952                 if (keybinding->request != request)
953                         continue;
955                 for (key = 0; key < ARRAY_SIZE(key_table); key++)
956                         if (key_table[key].value == keybinding->alias)
957                                 seq = key_table[key].name;
959                 if (seq == NULL &&
960                     keybinding->alias < 127 &&
961                     isprint(keybinding->alias)) {
962                         key_char[1] = (char) keybinding->alias;
963                         seq = key_char;
964                 }
966                 if (!seq)
967                         seq = "'?'";
969                 if (!string_format_from(buf, &pos, "%s%s", sep, seq))
970                         return "Too many keybindings!";
971                 sep = ", ";
972         }
974         return buf;
977 struct run_request {
978         enum keymap keymap;
979         int key;
980         char cmd[SIZEOF_STR];
981 };
983 static struct run_request *run_request;
984 static size_t run_requests;
986 static enum request
987 add_run_request(enum keymap keymap, int key, int argc, char **argv)
989         struct run_request *tmp;
990         struct run_request req = { keymap, key };
991         size_t bufpos;
993         for (bufpos = 0; argc > 0; argc--, argv++)
994                 if (!string_format_from(req.cmd, &bufpos, "%s ", *argv))
995                         return REQ_NONE;
997         req.cmd[bufpos - 1] = 0;
999         tmp = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
1000         if (!tmp)
1001                 return REQ_NONE;
1003         run_request = tmp;
1004         run_request[run_requests++] = req;
1006         return REQ_NONE + run_requests;
1009 static struct run_request *
1010 get_run_request(enum request request)
1012         if (request <= REQ_NONE)
1013                 return NULL;
1014         return &run_request[request - REQ_NONE - 1];
1017 static void
1018 add_builtin_run_requests(void)
1020         struct {
1021                 enum keymap keymap;
1022                 int key;
1023                 char *argv[1];
1024         } reqs[] = {
1025                 { KEYMAP_MAIN,    'C', { "git cherry-pick %(commit)" } },
1026                 { KEYMAP_GENERIC, 'G', { "git gc" } },
1027         };
1028         int i;
1030         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1031                 enum request req;
1033                 req = add_run_request(reqs[i].keymap, reqs[i].key, 1, reqs[i].argv);
1034                 if (req != REQ_NONE)
1035                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1036         }
1039 /*
1040  * User config file handling.
1041  */
1043 static struct int_map color_map[] = {
1044 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
1045         COLOR_MAP(DEFAULT),
1046         COLOR_MAP(BLACK),
1047         COLOR_MAP(BLUE),
1048         COLOR_MAP(CYAN),
1049         COLOR_MAP(GREEN),
1050         COLOR_MAP(MAGENTA),
1051         COLOR_MAP(RED),
1052         COLOR_MAP(WHITE),
1053         COLOR_MAP(YELLOW),
1054 };
1056 #define set_color(color, name) \
1057         set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
1059 static struct int_map attr_map[] = {
1060 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
1061         ATTR_MAP(NORMAL),
1062         ATTR_MAP(BLINK),
1063         ATTR_MAP(BOLD),
1064         ATTR_MAP(DIM),
1065         ATTR_MAP(REVERSE),
1066         ATTR_MAP(STANDOUT),
1067         ATTR_MAP(UNDERLINE),
1068 };
1070 #define set_attribute(attr, name) \
1071         set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
1073 static int   config_lineno;
1074 static bool  config_errors;
1075 static char *config_msg;
1077 /* Wants: object fgcolor bgcolor [attr] */
1078 static int
1079 option_color_command(int argc, char *argv[])
1081         struct line_info *info;
1083         if (argc != 3 && argc != 4) {
1084                 config_msg = "Wrong number of arguments given to color command";
1085                 return ERR;
1086         }
1088         info = get_line_info(argv[0], strlen(argv[0]));
1089         if (!info) {
1090                 config_msg = "Unknown color name";
1091                 return ERR;
1092         }
1094         if (set_color(&info->fg, argv[1]) == ERR ||
1095             set_color(&info->bg, argv[2]) == ERR) {
1096                 config_msg = "Unknown color";
1097                 return ERR;
1098         }
1100         if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1101                 config_msg = "Unknown attribute";
1102                 return ERR;
1103         }
1105         return OK;
1108 /* Wants: name = value */
1109 static int
1110 option_set_command(int argc, char *argv[])
1112         if (argc != 3) {
1113                 config_msg = "Wrong number of arguments given to set command";
1114                 return ERR;
1115         }
1117         if (strcmp(argv[1], "=")) {
1118                 config_msg = "No value assigned";
1119                 return ERR;
1120         }
1122         if (!strcmp(argv[0], "show-rev-graph")) {
1123                 opt_rev_graph = (!strcmp(argv[2], "1") ||
1124                                  !strcmp(argv[2], "true") ||
1125                                  !strcmp(argv[2], "yes"));
1126                 return OK;
1127         }
1129         if (!strcmp(argv[0], "line-number-interval")) {
1130                 opt_num_interval = atoi(argv[2]);
1131                 return OK;
1132         }
1134         if (!strcmp(argv[0], "tab-size")) {
1135                 opt_tab_size = atoi(argv[2]);
1136                 return OK;
1137         }
1139         if (!strcmp(argv[0], "commit-encoding")) {
1140                 char *arg = argv[2];
1141                 int delimiter = *arg;
1142                 int i;
1144                 switch (delimiter) {
1145                 case '"':
1146                 case '\'':
1147                         for (arg++, i = 0; arg[i]; i++)
1148                                 if (arg[i] == delimiter) {
1149                                         arg[i] = 0;
1150                                         break;
1151                                 }
1152                 default:
1153                         string_ncopy(opt_encoding, arg, strlen(arg));
1154                         return OK;
1155                 }
1156         }
1158         config_msg = "Unknown variable name";
1159         return ERR;
1162 /* Wants: mode request key */
1163 static int
1164 option_bind_command(int argc, char *argv[])
1166         enum request request;
1167         int keymap;
1168         int key;
1170         if (argc < 3) {
1171                 config_msg = "Wrong number of arguments given to bind command";
1172                 return ERR;
1173         }
1175         if (set_keymap(&keymap, argv[0]) == ERR) {
1176                 config_msg = "Unknown key map";
1177                 return ERR;
1178         }
1180         key = get_key_value(argv[1]);
1181         if (key == ERR) {
1182                 config_msg = "Unknown key";
1183                 return ERR;
1184         }
1186         request = get_request(argv[2]);
1187         if (request = REQ_NONE) {
1188                 const char *obsolete[] = { "cherry-pick" };
1189                 size_t namelen = strlen(argv[2]);
1190                 int i;
1192                 for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
1193                         if (namelen == strlen(obsolete[i]) &&
1194                             !string_enum_compare(obsolete[i], argv[2], namelen)) {
1195                                 config_msg = "Obsolete request name";
1196                                 return ERR;
1197                         }
1198                 }
1199         }
1200         if (request == REQ_NONE && *argv[2]++ == '!')
1201                 request = add_run_request(keymap, key, argc - 2, argv + 2);
1202         if (request == REQ_NONE) {
1203                 config_msg = "Unknown request name";
1204                 return ERR;
1205         }
1207         add_keybinding(keymap, request, key);
1209         return OK;
1212 static int
1213 set_option(char *opt, char *value)
1215         char *argv[16];
1216         int valuelen;
1217         int argc = 0;
1219         /* Tokenize */
1220         while (argc < ARRAY_SIZE(argv) && (valuelen = strcspn(value, " \t"))) {
1221                 argv[argc++] = value;
1222                 value += valuelen;
1224                 /* Nothing more to tokenize or last available token. */
1225                 if (!*value || argc >= ARRAY_SIZE(argv))
1226                         break;
1228                 *value++ = 0;
1229                 while (isspace(*value))
1230                         value++;
1231         }
1233         if (!strcmp(opt, "color"))
1234                 return option_color_command(argc, argv);
1236         if (!strcmp(opt, "set"))
1237                 return option_set_command(argc, argv);
1239         if (!strcmp(opt, "bind"))
1240                 return option_bind_command(argc, argv);
1242         config_msg = "Unknown option command";
1243         return ERR;
1246 static int
1247 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1249         int status = OK;
1251         config_lineno++;
1252         config_msg = "Internal error";
1254         /* Check for comment markers, since read_properties() will
1255          * only ensure opt and value are split at first " \t". */
1256         optlen = strcspn(opt, "#");
1257         if (optlen == 0)
1258                 return OK;
1260         if (opt[optlen] != 0) {
1261                 config_msg = "No option value";
1262                 status = ERR;
1264         }  else {
1265                 /* Look for comment endings in the value. */
1266                 size_t len = strcspn(value, "#");
1268                 if (len < valuelen) {
1269                         valuelen = len;
1270                         value[valuelen] = 0;
1271                 }
1273                 status = set_option(opt, value);
1274         }
1276         if (status == ERR) {
1277                 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1278                         config_lineno, (int) optlen, opt, config_msg);
1279                 config_errors = TRUE;
1280         }
1282         /* Always keep going if errors are encountered. */
1283         return OK;
1286 static int
1287 load_options(void)
1289         char *home = getenv("HOME");
1290         char buf[SIZEOF_STR];
1291         FILE *file;
1293         config_lineno = 0;
1294         config_errors = FALSE;
1296         add_builtin_run_requests();
1298         if (!home || !string_format(buf, "%s/.tigrc", home))
1299                 return ERR;
1301         /* It's ok that the file doesn't exist. */
1302         file = fopen(buf, "r");
1303         if (!file)
1304                 return OK;
1306         if (read_properties(file, " \t", read_option) == ERR ||
1307             config_errors == TRUE)
1308                 fprintf(stderr, "Errors while loading %s.\n", buf);
1310         return OK;
1314 /*
1315  * The viewer
1316  */
1318 struct view;
1319 struct view_ops;
1321 /* The display array of active views and the index of the current view. */
1322 static struct view *display[2];
1323 static unsigned int current_view;
1325 /* Reading from the prompt? */
1326 static bool input_mode = FALSE;
1328 #define foreach_displayed_view(view, i) \
1329         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1331 #define displayed_views()       (display[1] != NULL ? 2 : 1)
1333 /* Current head and commit ID */
1334 static char ref_blob[SIZEOF_REF]        = "";
1335 static char ref_commit[SIZEOF_REF]      = "HEAD";
1336 static char ref_head[SIZEOF_REF]        = "HEAD";
1338 struct view {
1339         const char *name;       /* View name */
1340         const char *cmd_fmt;    /* Default command line format */
1341         const char *cmd_env;    /* Command line set via environment */
1342         const char *id;         /* Points to either of ref_{head,commit,blob} */
1344         struct view_ops *ops;   /* View operations */
1346         enum keymap keymap;     /* What keymap does this view have */
1348         char cmd[SIZEOF_STR];   /* Command buffer */
1349         char ref[SIZEOF_REF];   /* Hovered commit reference */
1350         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
1352         int height, width;      /* The width and height of the main window */
1353         WINDOW *win;            /* The main window */
1354         WINDOW *title;          /* The title window living below the main window */
1356         /* Navigation */
1357         unsigned long offset;   /* Offset of the window top */
1358         unsigned long lineno;   /* Current line number */
1360         /* Searching */
1361         char grep[SIZEOF_STR];  /* Search string */
1362         regex_t *regex;         /* Pre-compiled regex */
1364         /* If non-NULL, points to the view that opened this view. If this view
1365          * is closed tig will switch back to the parent view. */
1366         struct view *parent;
1368         /* Buffering */
1369         unsigned long lines;    /* Total number of lines */
1370         struct line *line;      /* Line index */
1371         unsigned long line_size;/* Total number of allocated lines */
1372         unsigned int digits;    /* Number of digits in the lines member. */
1374         /* Loading */
1375         FILE *pipe;
1376         time_t start_time;
1377 };
1379 struct view_ops {
1380         /* What type of content being displayed. Used in the title bar. */
1381         const char *type;
1382         /* Open and reads in all view content. */
1383         bool (*open)(struct view *view);
1384         /* Read one line; updates view->line. */
1385         bool (*read)(struct view *view, char *data);
1386         /* Draw one line; @lineno must be < view->height. */
1387         bool (*draw)(struct view *view, struct line *line, unsigned int lineno, bool selected);
1388         /* Depending on view handle a special requests. */
1389         enum request (*request)(struct view *view, enum request request, struct line *line);
1390         /* Search for regex in a line. */
1391         bool (*grep)(struct view *view, struct line *line);
1392         /* Select line */
1393         void (*select)(struct view *view, struct line *line);
1394 };
1396 static struct view_ops pager_ops;
1397 static struct view_ops main_ops;
1398 static struct view_ops tree_ops;
1399 static struct view_ops blob_ops;
1400 static struct view_ops help_ops;
1401 static struct view_ops status_ops;
1402 static struct view_ops stage_ops;
1404 #define VIEW_STR(name, cmd, env, ref, ops, map) \
1405         { name, cmd, #env, ref, ops, map}
1407 #define VIEW_(id, name, ops, ref) \
1408         VIEW_STR(name, TIG_##id##_CMD,  TIG_##id##_CMD, ref, ops, KEYMAP_##id)
1411 static struct view views[] = {
1412         VIEW_(MAIN,   "main",   &main_ops,   ref_head),
1413         VIEW_(DIFF,   "diff",   &pager_ops,  ref_commit),
1414         VIEW_(LOG,    "log",    &pager_ops,  ref_head),
1415         VIEW_(TREE,   "tree",   &tree_ops,   ref_commit),
1416         VIEW_(BLOB,   "blob",   &blob_ops,   ref_blob),
1417         VIEW_(HELP,   "help",   &help_ops,   ""),
1418         VIEW_(PAGER,  "pager",  &pager_ops,  "stdin"),
1419         VIEW_(STATUS, "status", &status_ops, ""),
1420         VIEW_(STAGE,  "stage",  &stage_ops,  ""),
1421 };
1423 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1425 #define foreach_view(view, i) \
1426         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1428 #define view_is_displayed(view) \
1429         (view == display[0] || view == display[1])
1431 static bool
1432 draw_view_line(struct view *view, unsigned int lineno)
1434         struct line *line;
1435         bool selected = (view->offset + lineno == view->lineno);
1436         bool draw_ok;
1438         assert(view_is_displayed(view));
1440         if (view->offset + lineno >= view->lines)
1441                 return FALSE;
1443         line = &view->line[view->offset + lineno];
1445         if (selected) {
1446                 line->selected = TRUE;
1447                 view->ops->select(view, line);
1448         } else if (line->selected) {
1449                 line->selected = FALSE;
1450                 wmove(view->win, lineno, 0);
1451                 wclrtoeol(view->win);
1452         }
1454         scrollok(view->win, FALSE);
1455         draw_ok = view->ops->draw(view, line, lineno, selected);
1456         scrollok(view->win, TRUE);
1458         return draw_ok;
1461 static void
1462 redraw_view_from(struct view *view, int lineno)
1464         assert(0 <= lineno && lineno < view->height);
1466         for (; lineno < view->height; lineno++) {
1467                 if (!draw_view_line(view, lineno))
1468                         break;
1469         }
1471         redrawwin(view->win);
1472         if (input_mode)
1473                 wnoutrefresh(view->win);
1474         else
1475                 wrefresh(view->win);
1478 static void
1479 redraw_view(struct view *view)
1481         wclear(view->win);
1482         redraw_view_from(view, 0);
1486 static void
1487 update_view_title(struct view *view)
1489         char buf[SIZEOF_STR];
1490         char state[SIZEOF_STR];
1491         size_t bufpos = 0, statelen = 0;
1493         assert(view_is_displayed(view));
1495         if (view != VIEW(REQ_VIEW_STATUS) && (view->lines || view->pipe)) {
1496                 unsigned int view_lines = view->offset + view->height;
1497                 unsigned int lines = view->lines
1498                                    ? MIN(view_lines, view->lines) * 100 / view->lines
1499                                    : 0;
1501                 string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
1502                                    view->ops->type,
1503                                    view->lineno + 1,
1504                                    view->lines,
1505                                    lines);
1507                 if (view->pipe) {
1508                         time_t secs = time(NULL) - view->start_time;
1510                         /* Three git seconds are a long time ... */
1511                         if (secs > 2)
1512                                 string_format_from(state, &statelen, " %lds", secs);
1513                 }
1514         }
1516         string_format_from(buf, &bufpos, "[%s]", view->name);
1517         if (*view->ref && bufpos < view->width) {
1518                 size_t refsize = strlen(view->ref);
1519                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
1521                 if (minsize < view->width)
1522                         refsize = view->width - minsize + 7;
1523                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
1524         }
1526         if (statelen && bufpos < view->width) {
1527                 string_format_from(buf, &bufpos, " %s", state);
1528         }
1530         if (view == display[current_view])
1531                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
1532         else
1533                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
1535         mvwaddnstr(view->title, 0, 0, buf, bufpos);
1536         wclrtoeol(view->title);
1537         wmove(view->title, 0, view->width - 1);
1539         if (input_mode)
1540                 wnoutrefresh(view->title);
1541         else
1542                 wrefresh(view->title);
1545 static void
1546 resize_display(void)
1548         int offset, i;
1549         struct view *base = display[0];
1550         struct view *view = display[1] ? display[1] : display[0];
1552         /* Setup window dimensions */
1554         getmaxyx(stdscr, base->height, base->width);
1556         /* Make room for the status window. */
1557         base->height -= 1;
1559         if (view != base) {
1560                 /* Horizontal split. */
1561                 view->width   = base->width;
1562                 view->height  = SCALE_SPLIT_VIEW(base->height);
1563                 base->height -= view->height;
1565                 /* Make room for the title bar. */
1566                 view->height -= 1;
1567         }
1569         /* Make room for the title bar. */
1570         base->height -= 1;
1572         offset = 0;
1574         foreach_displayed_view (view, i) {
1575                 if (!view->win) {
1576                         view->win = newwin(view->height, 0, offset, 0);
1577                         if (!view->win)
1578                                 die("Failed to create %s view", view->name);
1580                         scrollok(view->win, TRUE);
1582                         view->title = newwin(1, 0, offset + view->height, 0);
1583                         if (!view->title)
1584                                 die("Failed to create title window");
1586                 } else {
1587                         wresize(view->win, view->height, view->width);
1588                         mvwin(view->win,   offset, 0);
1589                         mvwin(view->title, offset + view->height, 0);
1590                 }
1592                 offset += view->height + 1;
1593         }
1596 static void
1597 redraw_display(void)
1599         struct view *view;
1600         int i;
1602         foreach_displayed_view (view, i) {
1603                 redraw_view(view);
1604                 update_view_title(view);
1605         }
1608 static void
1609 update_display_cursor(struct view *view)
1611         /* Move the cursor to the right-most column of the cursor line.
1612          *
1613          * XXX: This could turn out to be a bit expensive, but it ensures that
1614          * the cursor does not jump around. */
1615         if (view->lines) {
1616                 wmove(view->win, view->lineno - view->offset, view->width - 1);
1617                 wrefresh(view->win);
1618         }
1621 /*
1622  * Navigation
1623  */
1625 /* Scrolling backend */
1626 static void
1627 do_scroll_view(struct view *view, int lines)
1629         bool redraw_current_line = FALSE;
1631         /* The rendering expects the new offset. */
1632         view->offset += lines;
1634         assert(0 <= view->offset && view->offset < view->lines);
1635         assert(lines);
1637         /* Move current line into the view. */
1638         if (view->lineno < view->offset) {
1639                 view->lineno = view->offset;
1640                 redraw_current_line = TRUE;
1641         } else if (view->lineno >= view->offset + view->height) {
1642                 view->lineno = view->offset + view->height - 1;
1643                 redraw_current_line = TRUE;
1644         }
1646         assert(view->offset <= view->lineno && view->lineno < view->lines);
1648         /* Redraw the whole screen if scrolling is pointless. */
1649         if (view->height < ABS(lines)) {
1650                 redraw_view(view);
1652         } else {
1653                 int line = lines > 0 ? view->height - lines : 0;
1654                 int end = line + ABS(lines);
1656                 wscrl(view->win, lines);
1658                 for (; line < end; line++) {
1659                         if (!draw_view_line(view, line))
1660                                 break;
1661                 }
1663                 if (redraw_current_line)
1664                         draw_view_line(view, view->lineno - view->offset);
1665         }
1667         redrawwin(view->win);
1668         wrefresh(view->win);
1669         report("");
1672 /* Scroll frontend */
1673 static void
1674 scroll_view(struct view *view, enum request request)
1676         int lines = 1;
1678         assert(view_is_displayed(view));
1680         switch (request) {
1681         case REQ_SCROLL_PAGE_DOWN:
1682                 lines = view->height;
1683         case REQ_SCROLL_LINE_DOWN:
1684                 if (view->offset + lines > view->lines)
1685                         lines = view->lines - view->offset;
1687                 if (lines == 0 || view->offset + view->height >= view->lines) {
1688                         report("Cannot scroll beyond the last line");
1689                         return;
1690                 }
1691                 break;
1693         case REQ_SCROLL_PAGE_UP:
1694                 lines = view->height;
1695         case REQ_SCROLL_LINE_UP:
1696                 if (lines > view->offset)
1697                         lines = view->offset;
1699                 if (lines == 0) {
1700                         report("Cannot scroll beyond the first line");
1701                         return;
1702                 }
1704                 lines = -lines;
1705                 break;
1707         default:
1708                 die("request %d not handled in switch", request);
1709         }
1711         do_scroll_view(view, lines);
1714 /* Cursor moving */
1715 static void
1716 move_view(struct view *view, enum request request)
1718         int scroll_steps = 0;
1719         int steps;
1721         switch (request) {
1722         case REQ_MOVE_FIRST_LINE:
1723                 steps = -view->lineno;
1724                 break;
1726         case REQ_MOVE_LAST_LINE:
1727                 steps = view->lines - view->lineno - 1;
1728                 break;
1730         case REQ_MOVE_PAGE_UP:
1731                 steps = view->height > view->lineno
1732                       ? -view->lineno : -view->height;
1733                 break;
1735         case REQ_MOVE_PAGE_DOWN:
1736                 steps = view->lineno + view->height >= view->lines
1737                       ? view->lines - view->lineno - 1 : view->height;
1738                 break;
1740         case REQ_MOVE_UP:
1741                 steps = -1;
1742                 break;
1744         case REQ_MOVE_DOWN:
1745                 steps = 1;
1746                 break;
1748         default:
1749                 die("request %d not handled in switch", request);
1750         }
1752         if (steps <= 0 && view->lineno == 0) {
1753                 report("Cannot move beyond the first line");
1754                 return;
1756         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
1757                 report("Cannot move beyond the last line");
1758                 return;
1759         }
1761         /* Move the current line */
1762         view->lineno += steps;
1763         assert(0 <= view->lineno && view->lineno < view->lines);
1765         /* Check whether the view needs to be scrolled */
1766         if (view->lineno < view->offset ||
1767             view->lineno >= view->offset + view->height) {
1768                 scroll_steps = steps;
1769                 if (steps < 0 && -steps > view->offset) {
1770                         scroll_steps = -view->offset;
1772                 } else if (steps > 0) {
1773                         if (view->lineno == view->lines - 1 &&
1774                             view->lines > view->height) {
1775                                 scroll_steps = view->lines - view->offset - 1;
1776                                 if (scroll_steps >= view->height)
1777                                         scroll_steps -= view->height - 1;
1778                         }
1779                 }
1780         }
1782         if (!view_is_displayed(view)) {
1783                 view->offset += scroll_steps;
1784                 assert(0 <= view->offset && view->offset < view->lines);
1785                 view->ops->select(view, &view->line[view->lineno]);
1786                 return;
1787         }
1789         /* Repaint the old "current" line if we be scrolling */
1790         if (ABS(steps) < view->height)
1791                 draw_view_line(view, view->lineno - steps - view->offset);
1793         if (scroll_steps) {
1794                 do_scroll_view(view, scroll_steps);
1795                 return;
1796         }
1798         /* Draw the current line */
1799         draw_view_line(view, view->lineno - view->offset);
1801         redrawwin(view->win);
1802         wrefresh(view->win);
1803         report("");
1807 /*
1808  * Searching
1809  */
1811 static void search_view(struct view *view, enum request request);
1813 static bool
1814 find_next_line(struct view *view, unsigned long lineno, struct line *line)
1816         assert(view_is_displayed(view));
1818         if (!view->ops->grep(view, line))
1819                 return FALSE;
1821         if (lineno - view->offset >= view->height) {
1822                 view->offset = lineno;
1823                 view->lineno = lineno;
1824                 redraw_view(view);
1826         } else {
1827                 unsigned long old_lineno = view->lineno - view->offset;
1829                 view->lineno = lineno;
1830                 draw_view_line(view, old_lineno);
1832                 draw_view_line(view, view->lineno - view->offset);
1833                 redrawwin(view->win);
1834                 wrefresh(view->win);
1835         }
1837         report("Line %ld matches '%s'", lineno + 1, view->grep);
1838         return TRUE;
1841 static void
1842 find_next(struct view *view, enum request request)
1844         unsigned long lineno = view->lineno;
1845         int direction;
1847         if (!*view->grep) {
1848                 if (!*opt_search)
1849                         report("No previous search");
1850                 else
1851                         search_view(view, request);
1852                 return;
1853         }
1855         switch (request) {
1856         case REQ_SEARCH:
1857         case REQ_FIND_NEXT:
1858                 direction = 1;
1859                 break;
1861         case REQ_SEARCH_BACK:
1862         case REQ_FIND_PREV:
1863                 direction = -1;
1864                 break;
1866         default:
1867                 return;
1868         }
1870         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
1871                 lineno += direction;
1873         /* Note, lineno is unsigned long so will wrap around in which case it
1874          * will become bigger than view->lines. */
1875         for (; lineno < view->lines; lineno += direction) {
1876                 struct line *line = &view->line[lineno];
1878                 if (find_next_line(view, lineno, line))
1879                         return;
1880         }
1882         report("No match found for '%s'", view->grep);
1885 static void
1886 search_view(struct view *view, enum request request)
1888         int regex_err;
1890         if (view->regex) {
1891                 regfree(view->regex);
1892                 *view->grep = 0;
1893         } else {
1894                 view->regex = calloc(1, sizeof(*view->regex));
1895                 if (!view->regex)
1896                         return;
1897         }
1899         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
1900         if (regex_err != 0) {
1901                 char buf[SIZEOF_STR] = "unknown error";
1903                 regerror(regex_err, view->regex, buf, sizeof(buf));
1904                 report("Search failed: %s", buf);
1905                 return;
1906         }
1908         string_copy(view->grep, opt_search);
1910         find_next(view, request);
1913 /*
1914  * Incremental updating
1915  */
1917 static void
1918 end_update(struct view *view)
1920         if (!view->pipe)
1921                 return;
1922         set_nonblocking_input(FALSE);
1923         if (view->pipe == stdin)
1924                 fclose(view->pipe);
1925         else
1926                 pclose(view->pipe);
1927         view->pipe = NULL;
1930 static bool
1931 begin_update(struct view *view)
1933         if (view->pipe)
1934                 end_update(view);
1936         if (opt_cmd[0]) {
1937                 string_copy(view->cmd, opt_cmd);
1938                 opt_cmd[0] = 0;
1939                 /* When running random commands, initially show the
1940                  * command in the title. However, it maybe later be
1941                  * overwritten if a commit line is selected. */
1942                 if (view == VIEW(REQ_VIEW_PAGER))
1943                         string_copy(view->ref, view->cmd);
1944                 else
1945                         view->ref[0] = 0;
1947         } else if (view == VIEW(REQ_VIEW_TREE)) {
1948                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
1949                 char path[SIZEOF_STR];
1951                 if (strcmp(view->vid, view->id))
1952                         opt_path[0] = path[0] = 0;
1953                 else if (sq_quote(path, 0, opt_path) >= sizeof(path))
1954                         return FALSE;
1956                 if (!string_format(view->cmd, format, view->id, path))
1957                         return FALSE;
1959         } else {
1960                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
1961                 const char *id = view->id;
1963                 if (!string_format(view->cmd, format, id, id, id, id, id))
1964                         return FALSE;
1966                 /* Put the current ref_* value to the view title ref
1967                  * member. This is needed by the blob view. Most other
1968                  * views sets it automatically after loading because the
1969                  * first line is a commit line. */
1970                 string_copy_rev(view->ref, view->id);
1971         }
1973         /* Special case for the pager view. */
1974         if (opt_pipe) {
1975                 view->pipe = opt_pipe;
1976                 opt_pipe = NULL;
1977         } else {
1978                 view->pipe = popen(view->cmd, "r");
1979         }
1981         if (!view->pipe)
1982                 return FALSE;
1984         set_nonblocking_input(TRUE);
1986         view->offset = 0;
1987         view->lines  = 0;
1988         view->lineno = 0;
1989         string_copy_rev(view->vid, view->id);
1991         if (view->line) {
1992                 int i;
1994                 for (i = 0; i < view->lines; i++)
1995                         if (view->line[i].data)
1996                                 free(view->line[i].data);
1998                 free(view->line);
1999                 view->line = NULL;
2000         }
2002         view->start_time = time(NULL);
2004         return TRUE;
2007 static struct line *
2008 realloc_lines(struct view *view, size_t line_size)
2010         struct line *tmp = realloc(view->line, sizeof(*view->line) * line_size);
2012         if (!tmp)
2013                 return NULL;
2015         view->line = tmp;
2016         view->line_size = line_size;
2017         return view->line;
2020 static bool
2021 update_view(struct view *view)
2023         char in_buffer[BUFSIZ];
2024         char out_buffer[BUFSIZ * 2];
2025         char *line;
2026         /* The number of lines to read. If too low it will cause too much
2027          * redrawing (and possible flickering), if too high responsiveness
2028          * will suffer. */
2029         unsigned long lines = view->height;
2030         int redraw_from = -1;
2032         if (!view->pipe)
2033                 return TRUE;
2035         /* Only redraw if lines are visible. */
2036         if (view->offset + view->height >= view->lines)
2037                 redraw_from = view->lines - view->offset;
2039         /* FIXME: This is probably not perfect for backgrounded views. */
2040         if (!realloc_lines(view, view->lines + lines))
2041                 goto alloc_error;
2043         while ((line = fgets(in_buffer, sizeof(in_buffer), view->pipe))) {
2044                 size_t linelen = strlen(line);
2046                 if (linelen)
2047                         line[linelen - 1] = 0;
2049                 if (opt_iconv != ICONV_NONE) {
2050                         ICONV_CONST char *inbuf = line;
2051                         size_t inlen = linelen;
2053                         char *outbuf = out_buffer;
2054                         size_t outlen = sizeof(out_buffer);
2056                         size_t ret;
2058                         ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2059                         if (ret != (size_t) -1) {
2060                                 line = out_buffer;
2061                                 linelen = strlen(out_buffer);
2062                         }
2063                 }
2065                 if (!view->ops->read(view, line))
2066                         goto alloc_error;
2068                 if (lines-- == 1)
2069                         break;
2070         }
2072         {
2073                 int digits;
2075                 lines = view->lines;
2076                 for (digits = 0; lines; digits++)
2077                         lines /= 10;
2079                 /* Keep the displayed view in sync with line number scaling. */
2080                 if (digits != view->digits) {
2081                         view->digits = digits;
2082                         redraw_from = 0;
2083                 }
2084         }
2086         if (!view_is_displayed(view))
2087                 goto check_pipe;
2089         if (view == VIEW(REQ_VIEW_TREE)) {
2090                 /* Clear the view and redraw everything since the tree sorting
2091                  * might have rearranged things. */
2092                 redraw_view(view);
2094         } else if (redraw_from >= 0) {
2095                 /* If this is an incremental update, redraw the previous line
2096                  * since for commits some members could have changed when
2097                  * loading the main view. */
2098                 if (redraw_from > 0)
2099                         redraw_from--;
2101                 /* Since revision graph visualization requires knowledge
2102                  * about the parent commit, it causes a further one-off
2103                  * needed to be redrawn for incremental updates. */
2104                 if (redraw_from > 0 && opt_rev_graph)
2105                         redraw_from--;
2107                 /* Incrementally draw avoids flickering. */
2108                 redraw_view_from(view, redraw_from);
2109         }
2111         /* Update the title _after_ the redraw so that if the redraw picks up a
2112          * commit reference in view->ref it'll be available here. */
2113         update_view_title(view);
2115 check_pipe:
2116         if (ferror(view->pipe)) {
2117                 report("Failed to read: %s", strerror(errno));
2118                 goto end;
2120         } else if (feof(view->pipe)) {
2121                 report("");
2122                 goto end;
2123         }
2125         return TRUE;
2127 alloc_error:
2128         report("Allocation failure");
2130 end:
2131         view->ops->read(view, NULL);
2132         end_update(view);
2133         return FALSE;
2136 static struct line *
2137 add_line_data(struct view *view, void *data, enum line_type type)
2139         struct line *line = &view->line[view->lines++];
2141         memset(line, 0, sizeof(*line));
2142         line->type = type;
2143         line->data = data;
2145         return line;
2148 static struct line *
2149 add_line_text(struct view *view, char *data, enum line_type type)
2151         if (data)
2152                 data = strdup(data);
2154         return data ? add_line_data(view, data, type) : NULL;
2158 /*
2159  * View opening
2160  */
2162 enum open_flags {
2163         OPEN_DEFAULT = 0,       /* Use default view switching. */
2164         OPEN_SPLIT = 1,         /* Split current view. */
2165         OPEN_BACKGROUNDED = 2,  /* Backgrounded. */
2166         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
2167 };
2169 static void
2170 open_view(struct view *prev, enum request request, enum open_flags flags)
2172         bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2173         bool split = !!(flags & OPEN_SPLIT);
2174         bool reload = !!(flags & OPEN_RELOAD);
2175         struct view *view = VIEW(request);
2176         int nviews = displayed_views();
2177         struct view *base_view = display[0];
2179         if (view == prev && nviews == 1 && !reload) {
2180                 report("Already in %s view", view->name);
2181                 return;
2182         }
2184         if (view->ops->open) {
2185                 if (!view->ops->open(view)) {
2186                         report("Failed to load %s view", view->name);
2187                         return;
2188                 }
2190         } else if ((reload || strcmp(view->vid, view->id)) &&
2191                    !begin_update(view)) {
2192                 report("Failed to load %s view", view->name);
2193                 return;
2194         }
2196         if (split) {
2197                 display[1] = view;
2198                 if (!backgrounded)
2199                         current_view = 1;
2200         } else {
2201                 /* Maximize the current view. */
2202                 memset(display, 0, sizeof(display));
2203                 current_view = 0;
2204                 display[current_view] = view;
2205         }
2207         /* Resize the view when switching between split- and full-screen,
2208          * or when switching between two different full-screen views. */
2209         if (nviews != displayed_views() ||
2210             (nviews == 1 && base_view != display[0]))
2211                 resize_display();
2213         if (split && prev->lineno - prev->offset >= prev->height) {
2214                 /* Take the title line into account. */
2215                 int lines = prev->lineno - prev->offset - prev->height + 1;
2217                 /* Scroll the view that was split if the current line is
2218                  * outside the new limited view. */
2219                 do_scroll_view(prev, lines);
2220         }
2222         if (prev && view != prev) {
2223                 if (split && !backgrounded) {
2224                         /* "Blur" the previous view. */
2225                         update_view_title(prev);
2226                 }
2228                 view->parent = prev;
2229         }
2231         if (view->pipe && view->lines == 0) {
2232                 /* Clear the old view and let the incremental updating refill
2233                  * the screen. */
2234                 wclear(view->win);
2235                 report("");
2236         } else {
2237                 redraw_view(view);
2238                 report("");
2239         }
2241         /* If the view is backgrounded the above calls to report()
2242          * won't redraw the view title. */
2243         if (backgrounded)
2244                 update_view_title(view);
2247 static void
2248 open_external_viewer(const char *cmd)
2250         def_prog_mode();           /* save current tty modes */
2251         endwin();                  /* restore original tty modes */
2252         system(cmd);
2253         fprintf(stderr, "Press Enter to continue");
2254         getc(stdin);
2255         reset_prog_mode();
2256         redraw_display();
2259 static void
2260 open_mergetool(const char *file)
2262         char cmd[SIZEOF_STR];
2263         char file_sq[SIZEOF_STR];
2265         if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2266             string_format(cmd, "git mergetool %s", file_sq)) {
2267                 open_external_viewer(cmd);
2268         }
2271 static void
2272 open_editor(bool from_root, const char *file)
2274         char cmd[SIZEOF_STR];
2275         char file_sq[SIZEOF_STR];
2276         char *editor;
2277         char *prefix = from_root ? opt_cdup : "";
2279         editor = getenv("GIT_EDITOR");
2280         if (!editor && *opt_editor)
2281                 editor = opt_editor;
2282         if (!editor)
2283                 editor = getenv("VISUAL");
2284         if (!editor)
2285                 editor = getenv("EDITOR");
2286         if (!editor)
2287                 editor = "vi";
2289         if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2290             string_format(cmd, "%s %s%s", editor, prefix, file_sq)) {
2291                 open_external_viewer(cmd);
2292         }
2295 static void
2296 open_run_request(enum request request)
2298         struct run_request *req = get_run_request(request);
2299         char buf[SIZEOF_STR * 2];
2300         size_t bufpos;
2301         char *cmd;
2303         if (!req) {
2304                 report("Unknown run request");
2305                 return;
2306         }
2308         bufpos = 0;
2309         cmd = req->cmd;
2311         while (cmd) {
2312                 char *next = strstr(cmd, "%(");
2313                 int len = next - cmd;
2314                 char *value;
2316                 if (!next) {
2317                         len = strlen(cmd);
2318                         value = "";
2320                 } else if (!strncmp(next, "%(head)", 7)) {
2321                         value = ref_head;
2323                 } else if (!strncmp(next, "%(commit)", 9)) {
2324                         value = ref_commit;
2326                 } else if (!strncmp(next, "%(blob)", 7)) {
2327                         value = ref_blob;
2329                 } else {
2330                         report("Unknown replacement in run request: `%s`", req->cmd);
2331                         return;
2332                 }
2334                 if (!string_format_from(buf, &bufpos, "%.*s%s", len, cmd, value))
2335                         return;
2337                 if (next)
2338                         next = strchr(next, ')') + 1;
2339                 cmd = next;
2340         }
2342         open_external_viewer(buf);
2345 /*
2346  * User request switch noodle
2347  */
2349 static int
2350 view_driver(struct view *view, enum request request)
2352         int i;
2354         if (request == REQ_NONE) {
2355                 doupdate();
2356                 return TRUE;
2357         }
2359         if (request > REQ_NONE) {
2360                 open_run_request(request);
2361                 return TRUE;
2362         }
2364         if (view && view->lines) {
2365                 request = view->ops->request(view, request, &view->line[view->lineno]);
2366                 if (request == REQ_NONE)
2367                         return TRUE;
2368         }
2370         switch (request) {
2371         case REQ_MOVE_UP:
2372         case REQ_MOVE_DOWN:
2373         case REQ_MOVE_PAGE_UP:
2374         case REQ_MOVE_PAGE_DOWN:
2375         case REQ_MOVE_FIRST_LINE:
2376         case REQ_MOVE_LAST_LINE:
2377                 move_view(view, request);
2378                 break;
2380         case REQ_SCROLL_LINE_DOWN:
2381         case REQ_SCROLL_LINE_UP:
2382         case REQ_SCROLL_PAGE_DOWN:
2383         case REQ_SCROLL_PAGE_UP:
2384                 scroll_view(view, request);
2385                 break;
2387         case REQ_VIEW_BLOB:
2388                 if (!ref_blob[0]) {
2389                         report("No file chosen, press %s to open tree view",
2390                                get_key(REQ_VIEW_TREE));
2391                         break;
2392                 }
2393                 open_view(view, request, OPEN_DEFAULT);
2394                 break;
2396         case REQ_VIEW_PAGER:
2397                 if (!opt_pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
2398                         report("No pager content, press %s to run command from prompt",
2399                                get_key(REQ_PROMPT));
2400                         break;
2401                 }
2402                 open_view(view, request, OPEN_DEFAULT);
2403                 break;
2405         case REQ_VIEW_STAGE:
2406                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
2407                         report("No stage content, press %s to open the status view and choose file",
2408                                get_key(REQ_VIEW_STATUS));
2409                         break;
2410                 }
2411                 open_view(view, request, OPEN_DEFAULT);
2412                 break;
2414         case REQ_VIEW_STATUS:
2415                 if (opt_is_inside_work_tree == FALSE) {
2416                         report("The status view requires a working tree");
2417                         break;
2418                 }
2419                 open_view(view, request, OPEN_DEFAULT);
2420                 break;
2422         case REQ_VIEW_MAIN:
2423         case REQ_VIEW_DIFF:
2424         case REQ_VIEW_LOG:
2425         case REQ_VIEW_TREE:
2426         case REQ_VIEW_HELP:
2427                 open_view(view, request, OPEN_DEFAULT);
2428                 break;
2430         case REQ_NEXT:
2431         case REQ_PREVIOUS:
2432                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
2434                 if ((view == VIEW(REQ_VIEW_DIFF) &&
2435                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
2436                    (view == VIEW(REQ_VIEW_STAGE) &&
2437                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
2438                    (view == VIEW(REQ_VIEW_BLOB) &&
2439                      view->parent == VIEW(REQ_VIEW_TREE))) {
2440                         int line;
2442                         view = view->parent;
2443                         line = view->lineno;
2444                         move_view(view, request);
2445                         if (view_is_displayed(view))
2446                                 update_view_title(view);
2447                         if (line != view->lineno)
2448                                 view->ops->request(view, REQ_ENTER,
2449                                                    &view->line[view->lineno]);
2451                 } else {
2452                         move_view(view, request);
2453                 }
2454                 break;
2456         case REQ_VIEW_NEXT:
2457         {
2458                 int nviews = displayed_views();
2459                 int next_view = (current_view + 1) % nviews;
2461                 if (next_view == current_view) {
2462                         report("Only one view is displayed");
2463                         break;
2464                 }
2466                 current_view = next_view;
2467                 /* Blur out the title of the previous view. */
2468                 update_view_title(view);
2469                 report("");
2470                 break;
2471         }
2472         case REQ_REFRESH:
2473                 report("Refreshing is not yet supported for the %s view", view->name);
2474                 break;
2476         case REQ_TOGGLE_LINENO:
2477                 opt_line_number = !opt_line_number;
2478                 redraw_display();
2479                 break;
2481         case REQ_TOGGLE_REV_GRAPH:
2482                 opt_rev_graph = !opt_rev_graph;
2483                 redraw_display();
2484                 break;
2486         case REQ_PROMPT:
2487                 /* Always reload^Wrerun commands from the prompt. */
2488                 open_view(view, opt_request, OPEN_RELOAD);
2489                 break;
2491         case REQ_SEARCH:
2492         case REQ_SEARCH_BACK:
2493                 search_view(view, request);
2494                 break;
2496         case REQ_FIND_NEXT:
2497         case REQ_FIND_PREV:
2498                 find_next(view, request);
2499                 break;
2501         case REQ_STOP_LOADING:
2502                 for (i = 0; i < ARRAY_SIZE(views); i++) {
2503                         view = &views[i];
2504                         if (view->pipe)
2505                                 report("Stopped loading the %s view", view->name),
2506                         end_update(view);
2507                 }
2508                 break;
2510         case REQ_SHOW_VERSION:
2511                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
2512                 return TRUE;
2514         case REQ_SCREEN_RESIZE:
2515                 resize_display();
2516                 /* Fall-through */
2517         case REQ_SCREEN_REDRAW:
2518                 redraw_display();
2519                 break;
2521         case REQ_EDIT:
2522                 report("Nothing to edit");
2523                 break;
2526         case REQ_ENTER:
2527                 report("Nothing to enter");
2528                 break;
2531         case REQ_VIEW_CLOSE:
2532                 /* XXX: Mark closed views by letting view->parent point to the
2533                  * view itself. Parents to closed view should never be
2534                  * followed. */
2535                 if (view->parent &&
2536                     view->parent->parent != view->parent) {
2537                         memset(display, 0, sizeof(display));
2538                         current_view = 0;
2539                         display[current_view] = view->parent;
2540                         view->parent = view;
2541                         resize_display();
2542                         redraw_display();
2543                         break;
2544                 }
2545                 /* Fall-through */
2546         case REQ_QUIT:
2547                 return FALSE;
2549         default:
2550                 /* An unknown key will show most commonly used commands. */
2551                 report("Unknown key, press 'h' for help");
2552                 return TRUE;
2553         }
2555         return TRUE;
2559 /*
2560  * Pager backend
2561  */
2563 static bool
2564 pager_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
2566         char *text = line->data;
2567         enum line_type type = line->type;
2568         int textlen = strlen(text);
2569         int attr;
2571         wmove(view->win, lineno, 0);
2573         if (selected) {
2574                 type = LINE_CURSOR;
2575                 wchgat(view->win, -1, 0, type, NULL);
2576         }
2578         attr = get_line_attr(type);
2579         wattrset(view->win, attr);
2581         if (opt_line_number || opt_tab_size < TABSIZE) {
2582                 static char spaces[] = "                    ";
2583                 int col_offset = 0, col = 0;
2585                 if (opt_line_number) {
2586                         unsigned long real_lineno = view->offset + lineno + 1;
2588                         if (real_lineno == 1 ||
2589                             (real_lineno % opt_num_interval) == 0) {
2590                                 wprintw(view->win, "%.*d", view->digits, real_lineno);
2592                         } else {
2593                                 waddnstr(view->win, spaces,
2594                                          MIN(view->digits, STRING_SIZE(spaces)));
2595                         }
2596                         waddstr(view->win, ": ");
2597                         col_offset = view->digits + 2;
2598                 }
2600                 while (text && col_offset + col < view->width) {
2601                         int cols_max = view->width - col_offset - col;
2602                         char *pos = text;
2603                         int cols;
2605                         if (*text == '\t') {
2606                                 text++;
2607                                 assert(sizeof(spaces) > TABSIZE);
2608                                 pos = spaces;
2609                                 cols = opt_tab_size - (col % opt_tab_size);
2611                         } else {
2612                                 text = strchr(text, '\t');
2613                                 cols = line ? text - pos : strlen(pos);
2614                         }
2616                         waddnstr(view->win, pos, MIN(cols, cols_max));
2617                         col += cols;
2618                 }
2620         } else {
2621                 int col = 0, pos = 0;
2623                 for (; pos < textlen && col < view->width; pos++, col++)
2624                         if (text[pos] == '\t')
2625                                 col += TABSIZE - (col % TABSIZE) - 1;
2627                 waddnstr(view->win, text, pos);
2628         }
2630         return TRUE;
2633 static bool
2634 add_describe_ref(char *buf, size_t *bufpos, char *commit_id, const char *sep)
2636         char refbuf[SIZEOF_STR];
2637         char *ref = NULL;
2638         FILE *pipe;
2640         if (!string_format(refbuf, "git describe %s 2>/dev/null", commit_id))
2641                 return TRUE;
2643         pipe = popen(refbuf, "r");
2644         if (!pipe)
2645                 return TRUE;
2647         if ((ref = fgets(refbuf, sizeof(refbuf), pipe)))
2648                 ref = chomp_string(ref);
2649         pclose(pipe);
2651         if (!ref || !*ref)
2652                 return TRUE;
2654         /* This is the only fatal call, since it can "corrupt" the buffer. */
2655         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
2656                 return FALSE;
2658         return TRUE;
2661 static void
2662 add_pager_refs(struct view *view, struct line *line)
2664         char buf[SIZEOF_STR];
2665         char *commit_id = line->data + STRING_SIZE("commit ");
2666         struct ref **refs;
2667         size_t bufpos = 0, refpos = 0;
2668         const char *sep = "Refs: ";
2669         bool is_tag = FALSE;
2671         assert(line->type == LINE_COMMIT);
2673         refs = get_refs(commit_id);
2674         if (!refs) {
2675                 if (view == VIEW(REQ_VIEW_DIFF))
2676                         goto try_add_describe_ref;
2677                 return;
2678         }
2680         do {
2681                 struct ref *ref = refs[refpos];
2682                 char *fmt = ref->tag    ? "%s[%s]" :
2683                             ref->remote ? "%s<%s>" : "%s%s";
2685                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
2686                         return;
2687                 sep = ", ";
2688                 if (ref->tag)
2689                         is_tag = TRUE;
2690         } while (refs[refpos++]->next);
2692         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
2693 try_add_describe_ref:
2694                 /* Add <tag>-g<commit_id> "fake" reference. */
2695                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
2696                         return;
2697         }
2699         if (bufpos == 0)
2700                 return;
2702         if (!realloc_lines(view, view->line_size + 1))
2703                 return;
2705         add_line_text(view, buf, LINE_PP_REFS);
2708 static bool
2709 pager_read(struct view *view, char *data)
2711         struct line *line;
2713         if (!data)
2714                 return TRUE;
2716         line = add_line_text(view, data, get_line_type(data));
2717         if (!line)
2718                 return FALSE;
2720         if (line->type == LINE_COMMIT &&
2721             (view == VIEW(REQ_VIEW_DIFF) ||
2722              view == VIEW(REQ_VIEW_LOG)))
2723                 add_pager_refs(view, line);
2725         return TRUE;
2728 static enum request
2729 pager_request(struct view *view, enum request request, struct line *line)
2731         int split = 0;
2733         if (request != REQ_ENTER)
2734                 return request;
2736         if (line->type == LINE_COMMIT &&
2737            (view == VIEW(REQ_VIEW_LOG) ||
2738             view == VIEW(REQ_VIEW_PAGER))) {
2739                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
2740                 split = 1;
2741         }
2743         /* Always scroll the view even if it was split. That way
2744          * you can use Enter to scroll through the log view and
2745          * split open each commit diff. */
2746         scroll_view(view, REQ_SCROLL_LINE_DOWN);
2748         /* FIXME: A minor workaround. Scrolling the view will call report("")
2749          * but if we are scrolling a non-current view this won't properly
2750          * update the view title. */
2751         if (split)
2752                 update_view_title(view);
2754         return REQ_NONE;
2757 static bool
2758 pager_grep(struct view *view, struct line *line)
2760         regmatch_t pmatch;
2761         char *text = line->data;
2763         if (!*text)
2764                 return FALSE;
2766         if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
2767                 return FALSE;
2769         return TRUE;
2772 static void
2773 pager_select(struct view *view, struct line *line)
2775         if (line->type == LINE_COMMIT) {
2776                 char *text = line->data + STRING_SIZE("commit ");
2778                 if (view != VIEW(REQ_VIEW_PAGER))
2779                         string_copy_rev(view->ref, text);
2780                 string_copy_rev(ref_commit, text);
2781         }
2784 static struct view_ops pager_ops = {
2785         "line",
2786         NULL,
2787         pager_read,
2788         pager_draw,
2789         pager_request,
2790         pager_grep,
2791         pager_select,
2792 };
2795 /*
2796  * Help backend
2797  */
2799 static bool
2800 help_open(struct view *view)
2802         char buf[BUFSIZ];
2803         int lines = ARRAY_SIZE(req_info) + 2;
2804         int i;
2806         if (view->lines > 0)
2807                 return TRUE;
2809         for (i = 0; i < ARRAY_SIZE(req_info); i++)
2810                 if (!req_info[i].request)
2811                         lines++;
2813         lines += run_requests + 1;
2815         view->line = calloc(lines, sizeof(*view->line));
2816         if (!view->line)
2817                 return FALSE;
2819         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
2821         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
2822                 char *key;
2824                 if (req_info[i].request == REQ_NONE)
2825                         continue;
2827                 if (!req_info[i].request) {
2828                         add_line_text(view, "", LINE_DEFAULT);
2829                         add_line_text(view, req_info[i].help, LINE_DEFAULT);
2830                         continue;
2831                 }
2833                 key = get_key(req_info[i].request);
2834                 if (!*key)
2835                         key = "(no key defined)";
2837                 if (!string_format(buf, "    %-25s %s", key, req_info[i].help))
2838                         continue;
2840                 add_line_text(view, buf, LINE_DEFAULT);
2841         }
2843         if (run_requests) {
2844                 add_line_text(view, "", LINE_DEFAULT);
2845                 add_line_text(view, "External commands:", LINE_DEFAULT);
2846         }
2848         for (i = 0; i < run_requests; i++) {
2849                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
2850                 char *key;
2852                 if (!req)
2853                         continue;
2855                 key = get_key_name(req->key);
2856                 if (!*key)
2857                         key = "(no key defined)";
2859                 if (!string_format(buf, "    %-10s %-14s `%s`",
2860                                    keymap_table[req->keymap].name,
2861                                    key, req->cmd))
2862                         continue;
2864                 add_line_text(view, buf, LINE_DEFAULT);
2865         }
2867         return TRUE;
2870 static struct view_ops help_ops = {
2871         "line",
2872         help_open,
2873         NULL,
2874         pager_draw,
2875         pager_request,
2876         pager_grep,
2877         pager_select,
2878 };
2881 /*
2882  * Tree backend
2883  */
2885 struct tree_stack_entry {
2886         struct tree_stack_entry *prev;  /* Entry below this in the stack */
2887         unsigned long lineno;           /* Line number to restore */
2888         char *name;                     /* Position of name in opt_path */
2889 };
2891 /* The top of the path stack. */
2892 static struct tree_stack_entry *tree_stack = NULL;
2893 unsigned long tree_lineno = 0;
2895 static void
2896 pop_tree_stack_entry(void)
2898         struct tree_stack_entry *entry = tree_stack;
2900         tree_lineno = entry->lineno;
2901         entry->name[0] = 0;
2902         tree_stack = entry->prev;
2903         free(entry);
2906 static void
2907 push_tree_stack_entry(char *name, unsigned long lineno)
2909         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
2910         size_t pathlen = strlen(opt_path);
2912         if (!entry)
2913                 return;
2915         entry->prev = tree_stack;
2916         entry->name = opt_path + pathlen;
2917         tree_stack = entry;
2919         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
2920                 pop_tree_stack_entry();
2921                 return;
2922         }
2924         /* Move the current line to the first tree entry. */
2925         tree_lineno = 1;
2926         entry->lineno = lineno;
2929 /* Parse output from git-ls-tree(1):
2930  *
2931  * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
2932  * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
2933  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
2934  * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
2935  */
2937 #define SIZEOF_TREE_ATTR \
2938         STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
2940 #define TREE_UP_FORMAT "040000 tree %s\t.."
2942 static int
2943 tree_compare_entry(enum line_type type1, char *name1,
2944                    enum line_type type2, char *name2)
2946         if (type1 != type2) {
2947                 if (type1 == LINE_TREE_DIR)
2948                         return -1;
2949                 return 1;
2950         }
2952         return strcmp(name1, name2);
2955 static bool
2956 tree_read(struct view *view, char *text)
2958         size_t textlen = text ? strlen(text) : 0;
2959         char buf[SIZEOF_STR];
2960         unsigned long pos;
2961         enum line_type type;
2962         bool first_read = view->lines == 0;
2964         if (textlen <= SIZEOF_TREE_ATTR)
2965                 return FALSE;
2967         type = text[STRING_SIZE("100644 ")] == 't'
2968              ? LINE_TREE_DIR : LINE_TREE_FILE;
2970         if (first_read) {
2971                 /* Add path info line */
2972                 if (!string_format(buf, "Directory path /%s", opt_path) ||
2973                     !realloc_lines(view, view->line_size + 1) ||
2974                     !add_line_text(view, buf, LINE_DEFAULT))
2975                         return FALSE;
2977                 /* Insert "link" to parent directory. */
2978                 if (*opt_path) {
2979                         if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
2980                             !realloc_lines(view, view->line_size + 1) ||
2981                             !add_line_text(view, buf, LINE_TREE_DIR))
2982                                 return FALSE;
2983                 }
2984         }
2986         /* Strip the path part ... */
2987         if (*opt_path) {
2988                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
2989                 size_t striplen = strlen(opt_path);
2990                 char *path = text + SIZEOF_TREE_ATTR;
2992                 if (pathlen > striplen)
2993                         memmove(path, path + striplen,
2994                                 pathlen - striplen + 1);
2995         }
2997         /* Skip "Directory ..." and ".." line. */
2998         for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
2999                 struct line *line = &view->line[pos];
3000                 char *path1 = ((char *) line->data) + SIZEOF_TREE_ATTR;
3001                 char *path2 = text + SIZEOF_TREE_ATTR;
3002                 int cmp = tree_compare_entry(line->type, path1, type, path2);
3004                 if (cmp <= 0)
3005                         continue;
3007                 text = strdup(text);
3008                 if (!text)
3009                         return FALSE;
3011                 if (view->lines > pos)
3012                         memmove(&view->line[pos + 1], &view->line[pos],
3013                                 (view->lines - pos) * sizeof(*line));
3015                 line = &view->line[pos];
3016                 line->data = text;
3017                 line->type = type;
3018                 view->lines++;
3019                 return TRUE;
3020         }
3022         if (!add_line_text(view, text, type))
3023                 return FALSE;
3025         if (tree_lineno > view->lineno) {
3026                 view->lineno = tree_lineno;
3027                 tree_lineno = 0;
3028         }
3030         return TRUE;
3033 static enum request
3034 tree_request(struct view *view, enum request request, struct line *line)
3036         enum open_flags flags;
3038         if (request != REQ_ENTER)
3039                 return request;
3041         /* Cleanup the stack if the tree view is at a different tree. */
3042         while (!*opt_path && tree_stack)
3043                 pop_tree_stack_entry();
3045         switch (line->type) {
3046         case LINE_TREE_DIR:
3047                 /* Depending on whether it is a subdir or parent (updir?) link
3048                  * mangle the path buffer. */
3049                 if (line == &view->line[1] && *opt_path) {
3050                         pop_tree_stack_entry();
3052                 } else {
3053                         char *data = line->data;
3054                         char *basename = data + SIZEOF_TREE_ATTR;
3056                         push_tree_stack_entry(basename, view->lineno);
3057                 }
3059                 /* Trees and subtrees share the same ID, so they are not not
3060                  * unique like blobs. */
3061                 flags = OPEN_RELOAD;
3062                 request = REQ_VIEW_TREE;
3063                 break;
3065         case LINE_TREE_FILE:
3066                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3067                 request = REQ_VIEW_BLOB;
3068                 break;
3070         default:
3071                 return TRUE;
3072         }
3074         open_view(view, request, flags);
3075         if (request == REQ_VIEW_TREE) {
3076                 view->lineno = tree_lineno;
3077         }
3079         return REQ_NONE;
3082 static void
3083 tree_select(struct view *view, struct line *line)
3085         char *text = line->data + STRING_SIZE("100644 blob ");
3087         if (line->type == LINE_TREE_FILE) {
3088                 string_copy_rev(ref_blob, text);
3090         } else if (line->type != LINE_TREE_DIR) {
3091                 return;
3092         }
3094         string_copy_rev(view->ref, text);
3097 static struct view_ops tree_ops = {
3098         "file",
3099         NULL,
3100         tree_read,
3101         pager_draw,
3102         tree_request,
3103         pager_grep,
3104         tree_select,
3105 };
3107 static bool
3108 blob_read(struct view *view, char *line)
3110         return add_line_text(view, line, LINE_DEFAULT) != NULL;
3113 static struct view_ops blob_ops = {
3114         "line",
3115         NULL,
3116         blob_read,
3117         pager_draw,
3118         pager_request,
3119         pager_grep,
3120         pager_select,
3121 };
3124 /*
3125  * Status backend
3126  */
3128 struct status {
3129         char status;
3130         struct {
3131                 mode_t mode;
3132                 char rev[SIZEOF_REV];
3133         } old;
3134         struct {
3135                 mode_t mode;
3136                 char rev[SIZEOF_REV];
3137         } new;
3138         char name[SIZEOF_STR];
3139 };
3141 static struct status stage_status;
3142 static enum line_type stage_line_type;
3144 /* Get fields from the diff line:
3145  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
3146  */
3147 static inline bool
3148 status_get_diff(struct status *file, char *buf, size_t bufsize)
3150         char *old_mode = buf +  1;
3151         char *new_mode = buf +  8;
3152         char *old_rev  = buf + 15;
3153         char *new_rev  = buf + 56;
3154         char *status   = buf + 97;
3156         if (bufsize != 99 ||
3157             old_mode[-1] != ':' ||
3158             new_mode[-1] != ' ' ||
3159             old_rev[-1]  != ' ' ||
3160             new_rev[-1]  != ' ' ||
3161             status[-1]   != ' ')
3162                 return FALSE;
3164         file->status = *status;
3166         string_copy_rev(file->old.rev, old_rev);
3167         string_copy_rev(file->new.rev, new_rev);
3169         file->old.mode = strtoul(old_mode, NULL, 8);
3170         file->new.mode = strtoul(new_mode, NULL, 8);
3172         file->name[0] = 0;
3174         return TRUE;
3177 static bool
3178 status_run(struct view *view, const char cmd[], bool diff, enum line_type type)
3180         struct status *file = NULL;
3181         struct status *unmerged = NULL;
3182         char buf[SIZEOF_STR * 4];
3183         size_t bufsize = 0;
3184         FILE *pipe;
3186         pipe = popen(cmd, "r");
3187         if (!pipe)
3188                 return FALSE;
3190         add_line_data(view, NULL, type);
3192         while (!feof(pipe) && !ferror(pipe)) {
3193                 char *sep;
3194                 size_t readsize;
3196                 readsize = fread(buf + bufsize, 1, sizeof(buf) - bufsize, pipe);
3197                 if (!readsize)
3198                         break;
3199                 bufsize += readsize;
3201                 /* Process while we have NUL chars. */
3202                 while ((sep = memchr(buf, 0, bufsize))) {
3203                         size_t sepsize = sep - buf + 1;
3205                         if (!file) {
3206                                 if (!realloc_lines(view, view->line_size + 1))
3207                                         goto error_out;
3209                                 file = calloc(1, sizeof(*file));
3210                                 if (!file)
3211                                         goto error_out;
3213                                 add_line_data(view, file, type);
3214                         }
3216                         /* Parse diff info part. */
3217                         if (!diff) {
3218                                 file->status = '?';
3220                         } else if (!file->status) {
3221                                 if (!status_get_diff(file, buf, sepsize))
3222                                         goto error_out;
3224                                 bufsize -= sepsize;
3225                                 memmove(buf, sep + 1, bufsize);
3227                                 sep = memchr(buf, 0, bufsize);
3228                                 if (!sep)
3229                                         break;
3230                                 sepsize = sep - buf + 1;
3232                                 /* Collapse all 'M'odified entries that
3233                                  * follow a associated 'U'nmerged entry.
3234                                  */
3235                                 if (file->status == 'U') {
3236                                         unmerged = file;
3238                                 } else if (unmerged) {
3239                                         int collapse = !strcmp(buf, unmerged->name);
3241                                         unmerged = NULL;
3242                                         if (collapse) {
3243                                                 free(file);
3244                                                 view->lines--;
3245                                                 continue;
3246                                         }
3247                                 }
3248                         }
3250                         /* git-ls-files just delivers a NUL separated
3251                          * list of file names similar to the second half
3252                          * of the git-diff-* output. */
3253                         string_ncopy(file->name, buf, sepsize);
3254                         bufsize -= sepsize;
3255                         memmove(buf, sep + 1, bufsize);
3256                         file = NULL;
3257                 }
3258         }
3260         if (ferror(pipe)) {
3261 error_out:
3262                 pclose(pipe);
3263                 return FALSE;
3264         }
3266         if (!view->line[view->lines - 1].data)
3267                 add_line_data(view, NULL, LINE_STAT_NONE);
3269         pclose(pipe);
3270         return TRUE;
3273 /* Don't show unmerged entries in the staged section. */
3274 #define STATUS_DIFF_INDEX_CMD "git diff-index -z --diff-filter=ACDMRTXB --cached HEAD"
3275 #define STATUS_DIFF_FILES_CMD "git diff-files -z"
3276 #define STATUS_LIST_OTHER_CMD \
3277         "git ls-files -z --others --exclude-per-directory=.gitignore"
3279 #define STATUS_DIFF_SHOW_CMD \
3280         "git diff --root --patch-with-stat --find-copies-harder -B -C %s -- %s 2>/dev/null"
3282 /* First parse staged info using git-diff-index(1), then parse unstaged
3283  * info using git-diff-files(1), and finally untracked files using
3284  * git-ls-files(1). */
3285 static bool
3286 status_open(struct view *view)
3288         struct stat statbuf;
3289         char exclude[SIZEOF_STR];
3290         char cmd[SIZEOF_STR];
3291         unsigned long prev_lineno = view->lineno;
3292         size_t i;
3294         for (i = 0; i < view->lines; i++)
3295                 free(view->line[i].data);
3296         free(view->line);
3297         view->lines = view->line_size = view->lineno = 0;
3298         view->line = NULL;
3300         if (!realloc_lines(view, view->line_size + 6))
3301                 return FALSE;
3303         if (!string_format(exclude, "%s/info/exclude", opt_git_dir))
3304                 return FALSE;
3306         string_copy(cmd, STATUS_LIST_OTHER_CMD);
3308         if (stat(exclude, &statbuf) >= 0) {
3309                 size_t cmdsize = strlen(cmd);
3311                 if (!string_format_from(cmd, &cmdsize, " %s", "--exclude-from=") ||
3312                     sq_quote(cmd, cmdsize, exclude) >= sizeof(cmd))
3313                         return FALSE;
3314         }
3316         if (!status_run(view, STATUS_DIFF_INDEX_CMD, TRUE, LINE_STAT_STAGED) ||
3317             !status_run(view, STATUS_DIFF_FILES_CMD, TRUE, LINE_STAT_UNSTAGED) ||
3318             !status_run(view, cmd, FALSE, LINE_STAT_UNTRACKED))
3319                 return FALSE;
3321         /* If all went well restore the previous line number to stay in
3322          * the context. */
3323         if (prev_lineno < view->lines)
3324                 view->lineno = prev_lineno;
3325         else
3326                 view->lineno = view->lines - 1;
3328         return TRUE;
3331 static bool
3332 status_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
3334         struct status *status = line->data;
3336         wmove(view->win, lineno, 0);
3338         if (selected) {
3339                 wattrset(view->win, get_line_attr(LINE_CURSOR));
3340                 wchgat(view->win, -1, 0, LINE_CURSOR, NULL);
3342         } else if (!status && line->type != LINE_STAT_NONE) {
3343                 wattrset(view->win, get_line_attr(LINE_STAT_SECTION));
3344                 wchgat(view->win, -1, 0, LINE_STAT_SECTION, NULL);
3346         } else {
3347                 wattrset(view->win, get_line_attr(line->type));
3348         }
3350         if (!status) {
3351                 char *text;
3353                 switch (line->type) {
3354                 case LINE_STAT_STAGED:
3355                         text = "Changes to be committed:";
3356                         break;
3358                 case LINE_STAT_UNSTAGED:
3359                         text = "Changed but not updated:";
3360                         break;
3362                 case LINE_STAT_UNTRACKED:
3363                         text = "Untracked files:";
3364                         break;
3366                 case LINE_STAT_NONE:
3367                         text = "    (no files)";
3368                         break;
3370                 default:
3371                         return FALSE;
3372                 }
3374                 waddstr(view->win, text);
3375                 return TRUE;
3376         }
3378         waddch(view->win, status->status);
3379         if (!selected)
3380                 wattrset(view->win, A_NORMAL);
3381         wmove(view->win, lineno, 4);
3382         waddstr(view->win, status->name);
3384         return TRUE;
3387 static enum request
3388 status_enter(struct view *view, struct line *line)
3390         struct status *status = line->data;
3391         char path[SIZEOF_STR] = "";
3392         char *info;
3393         size_t cmdsize = 0;
3395         if (line->type == LINE_STAT_NONE ||
3396             (!status && line[1].type == LINE_STAT_NONE)) {
3397                 report("No file to diff");
3398                 return REQ_NONE;
3399         }
3401         if (status && sq_quote(path, 0, status->name) >= sizeof(path))
3402                 return REQ_QUIT;
3404         if (opt_cdup[0] &&
3405             line->type != LINE_STAT_UNTRACKED &&
3406             !string_format_from(opt_cmd, &cmdsize, "cd %s;", opt_cdup))
3407                 return REQ_QUIT;
3409         switch (line->type) {
3410         case LINE_STAT_STAGED:
3411                 if (!string_format_from(opt_cmd, &cmdsize, STATUS_DIFF_SHOW_CMD,
3412                                         "--cached", path))
3413                         return REQ_QUIT;
3414                 if (status)
3415                         info = "Staged changes to %s";
3416                 else
3417                         info = "Staged changes";
3418                 break;
3420         case LINE_STAT_UNSTAGED:
3421                 if (!string_format_from(opt_cmd, &cmdsize, STATUS_DIFF_SHOW_CMD,
3422                                         "", path))
3423                         return REQ_QUIT;
3424                 if (status)
3425                         info = "Unstaged changes to %s";
3426                 else
3427                         info = "Unstaged changes";
3428                 break;
3430         case LINE_STAT_UNTRACKED:
3431                 if (opt_pipe)
3432                         return REQ_QUIT;
3435                 if (!status) {
3436                         report("No file to show");
3437                         return REQ_NONE;
3438                 }
3440                 opt_pipe = fopen(status->name, "r");
3441                 info = "Untracked file %s";
3442                 break;
3444         default:
3445                 die("line type %d not handled in switch", line->type);
3446         }
3448         open_view(view, REQ_VIEW_STAGE, OPEN_RELOAD | OPEN_SPLIT);
3449         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
3450                 if (status) {
3451                         stage_status = *status;
3452                 } else {
3453                         memset(&stage_status, 0, sizeof(stage_status));
3454                 }
3456                 stage_line_type = line->type;
3457                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.name);
3458         }
3460         return REQ_NONE;
3464 static bool
3465 status_update_file(struct view *view, struct status *status, enum line_type type)
3467         char cmd[SIZEOF_STR];
3468         char buf[SIZEOF_STR];
3469         size_t cmdsize = 0;
3470         size_t bufsize = 0;
3471         size_t written = 0;
3472         FILE *pipe;
3474         if (opt_cdup[0] &&
3475             type != LINE_STAT_UNTRACKED &&
3476             !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
3477                 return FALSE;
3479         switch (type) {
3480         case LINE_STAT_STAGED:
3481                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
3482                                         status->old.mode,
3483                                         status->old.rev,
3484                                         status->name, 0))
3485                         return FALSE;
3487                 string_add(cmd, cmdsize, "git update-index -z --index-info");
3488                 break;
3490         case LINE_STAT_UNSTAGED:
3491         case LINE_STAT_UNTRACKED:
3492                 if (!string_format_from(buf, &bufsize, "%s%c", status->name, 0))
3493                         return FALSE;
3495                 string_add(cmd, cmdsize, "git update-index -z --add --remove --stdin");
3496                 break;
3498         default:
3499                 die("line type %d not handled in switch", type);
3500         }
3502         pipe = popen(cmd, "w");
3503         if (!pipe)
3504                 return FALSE;
3506         while (!ferror(pipe) && written < bufsize) {
3507                 written += fwrite(buf + written, 1, bufsize - written, pipe);
3508         }
3510         pclose(pipe);
3512         if (written != bufsize)
3513                 return FALSE;
3515         return TRUE;
3518 static void
3519 status_update(struct view *view)
3521         struct line *line = &view->line[view->lineno];
3523         assert(view->lines);
3525         if (!line->data) {
3526                 while (++line < view->line + view->lines && line->data) {
3527                         if (!status_update_file(view, line->data, line->type))
3528                                 report("Failed to update file status");
3529                 }
3531                 if (!line[-1].data) {
3532                         report("Nothing to update");
3533                         return;
3534                 }
3536         } else if (!status_update_file(view, line->data, line->type)) {
3537                 report("Failed to update file status");
3538         }
3541 static enum request
3542 status_request(struct view *view, enum request request, struct line *line)
3544         struct status *status = line->data;
3546         switch (request) {
3547         case REQ_STATUS_UPDATE:
3548                 status_update(view);
3549                 break;
3551         case REQ_STATUS_MERGE:
3552                 open_mergetool(status->name);
3553                 break;
3555         case REQ_EDIT:
3556                 if (!status)
3557                         return request;
3559                 open_editor(status->status != '?', status->name);
3560                 break;
3562         case REQ_ENTER:
3563                 /* After returning the status view has been split to
3564                  * show the stage view. No further reloading is
3565                  * necessary. */
3566                 status_enter(view, line);
3567                 return REQ_NONE;
3569         case REQ_REFRESH:
3570                 /* Simply reload the view. */
3571                 break;
3573         default:
3574                 return request;
3575         }
3577         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
3579         return REQ_NONE;
3582 static void
3583 status_select(struct view *view, struct line *line)
3585         struct status *status = line->data;
3586         char file[SIZEOF_STR] = "all files";
3587         char *text;
3588         char *key;
3590         if (status && !string_format(file, "'%s'", status->name))
3591                 return;
3593         if (!status && line[1].type == LINE_STAT_NONE)
3594                 line++;
3596         switch (line->type) {
3597         case LINE_STAT_STAGED:
3598                 text = "Press %s to unstage %s for commit";
3599                 break;
3601         case LINE_STAT_UNSTAGED:
3602                 text = "Press %s to stage %s for commit";
3603                 break;
3605         case LINE_STAT_UNTRACKED:
3606                 text = "Press %s to stage %s for addition";
3607                 break;
3609         case LINE_STAT_NONE:
3610                 text = "Nothing to update";
3611                 break;
3613         default:
3614                 die("line type %d not handled in switch", line->type);
3615         }
3617         if (status && status->status == 'U') {
3618                 text = "Press %s to resolve conflict in %s";
3619                 key = get_key(REQ_STATUS_MERGE);
3621         } else {
3622                 key = get_key(REQ_STATUS_UPDATE);
3623         }
3625         string_format(view->ref, text, key, file);
3628 static bool
3629 status_grep(struct view *view, struct line *line)
3631         struct status *status = line->data;
3632         enum { S_STATUS, S_NAME, S_END } state;
3633         char buf[2] = "?";
3634         regmatch_t pmatch;
3636         if (!status)
3637                 return FALSE;
3639         for (state = S_STATUS; state < S_END; state++) {
3640                 char *text;
3642                 switch (state) {
3643                 case S_NAME:    text = status->name;    break;
3644                 case S_STATUS:
3645                         buf[0] = status->status;
3646                         text = buf;
3647                         break;
3649                 default:
3650                         return FALSE;
3651                 }
3653                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
3654                         return TRUE;
3655         }
3657         return FALSE;
3660 static struct view_ops status_ops = {
3661         "file",
3662         status_open,
3663         NULL,
3664         status_draw,
3665         status_request,
3666         status_grep,
3667         status_select,
3668 };
3671 static bool
3672 stage_diff_line(FILE *pipe, struct line *line)
3674         char *buf = line->data;
3675         size_t bufsize = strlen(buf);
3676         size_t written = 0;
3678         while (!ferror(pipe) && written < bufsize) {
3679                 written += fwrite(buf + written, 1, bufsize - written, pipe);
3680         }
3682         fputc('\n', pipe);
3684         return written == bufsize;
3687 static struct line *
3688 stage_diff_hdr(struct view *view, struct line *line)
3690         int diff_hdr_dir = line->type == LINE_DIFF_CHUNK ? -1 : 1;
3691         struct line *diff_hdr;
3693         if (line->type == LINE_DIFF_CHUNK)
3694                 diff_hdr = line - 1;
3695         else
3696                 diff_hdr = view->line + 1;
3698         while (diff_hdr > view->line && diff_hdr < view->line + view->lines) {
3699                 if (diff_hdr->type == LINE_DIFF_HEADER)
3700                         return diff_hdr;
3702                 diff_hdr += diff_hdr_dir;
3703         }
3705         return NULL;
3708 static bool
3709 stage_update_chunk(struct view *view, struct line *line)
3711         char cmd[SIZEOF_STR];
3712         size_t cmdsize = 0;
3713         struct line *diff_hdr, *diff_chunk, *diff_end;
3714         FILE *pipe;
3716         diff_hdr = stage_diff_hdr(view, line);
3717         if (!diff_hdr)
3718                 return FALSE;
3720         if (opt_cdup[0] &&
3721             !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
3722                 return FALSE;
3724         if (!string_format_from(cmd, &cmdsize,
3725                                 "git apply --cached %s - && "
3726                                 "git update-index -q --unmerged --refresh 2>/dev/null",
3727                                 stage_line_type == LINE_STAT_STAGED ? "-R" : ""))
3728                 return FALSE;
3730         pipe = popen(cmd, "w");
3731         if (!pipe)
3732                 return FALSE;
3734         diff_end = view->line + view->lines;
3735         if (line->type != LINE_DIFF_CHUNK) {
3736                 diff_chunk = diff_hdr;
3738         } else {
3739                 for (diff_chunk = line + 1; diff_chunk < diff_end; diff_chunk++)
3740                         if (diff_chunk->type == LINE_DIFF_CHUNK ||
3741                             diff_chunk->type == LINE_DIFF_HEADER)
3742                                 diff_end = diff_chunk;
3744                 diff_chunk = line;
3746                 while (diff_hdr->type != LINE_DIFF_CHUNK) {
3747                         switch (diff_hdr->type) {
3748                         case LINE_DIFF_HEADER:
3749                         case LINE_DIFF_INDEX:
3750                         case LINE_DIFF_ADD:
3751                         case LINE_DIFF_DEL:
3752                                 break;
3754                         default:
3755                                 diff_hdr++;
3756                                 continue;
3757                         }
3759                         if (!stage_diff_line(pipe, diff_hdr++)) {
3760                                 pclose(pipe);
3761                                 return FALSE;
3762                         }
3763                 }
3764         }
3766         while (diff_chunk < diff_end && stage_diff_line(pipe, diff_chunk))
3767                 diff_chunk++;
3769         pclose(pipe);
3771         if (diff_chunk != diff_end)
3772                 return FALSE;
3774         return TRUE;
3777 static void
3778 stage_update(struct view *view, struct line *line)
3780         if (stage_line_type != LINE_STAT_UNTRACKED &&
3781             (line->type == LINE_DIFF_CHUNK || !stage_status.status)) {
3782                 if (!stage_update_chunk(view, line)) {
3783                         report("Failed to apply chunk");
3784                         return;
3785                 }
3787         } else if (!status_update_file(view, &stage_status, stage_line_type)) {
3788                 report("Failed to update file");
3789                 return;
3790         }
3792         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
3794         view = VIEW(REQ_VIEW_STATUS);
3795         if (view_is_displayed(view))
3796                 status_enter(view, &view->line[view->lineno]);
3799 static enum request
3800 stage_request(struct view *view, enum request request, struct line *line)
3802         switch (request) {
3803         case REQ_STATUS_UPDATE:
3804                 stage_update(view, line);
3805                 break;
3807         case REQ_EDIT:
3808                 if (!stage_status.name[0])
3809                         return request;
3811                 open_editor(stage_status.status != '?', stage_status.name);
3812                 break;
3814         case REQ_ENTER:
3815                 pager_request(view, request, line);
3816                 break;
3818         default:
3819                 return request;
3820         }
3822         return REQ_NONE;
3825 static struct view_ops stage_ops = {
3826         "line",
3827         NULL,
3828         pager_read,
3829         pager_draw,
3830         stage_request,
3831         pager_grep,
3832         pager_select,
3833 };
3836 /*
3837  * Revision graph
3838  */
3840 struct commit {
3841         char id[SIZEOF_REV];            /* SHA1 ID. */
3842         char title[128];                /* First line of the commit message. */
3843         char author[75];                /* Author of the commit. */
3844         struct tm time;                 /* Date from the author ident. */
3845         struct ref **refs;              /* Repository references. */
3846         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
3847         size_t graph_size;              /* The width of the graph array. */
3848 };
3850 /* Size of rev graph with no  "padding" columns */
3851 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
3853 struct rev_graph {
3854         struct rev_graph *prev, *next, *parents;
3855         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
3856         size_t size;
3857         struct commit *commit;
3858         size_t pos;
3859 };
3861 /* Parents of the commit being visualized. */
3862 static struct rev_graph graph_parents[4];
3864 /* The current stack of revisions on the graph. */
3865 static struct rev_graph graph_stacks[4] = {
3866         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
3867         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
3868         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
3869         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
3870 };
3872 static inline bool
3873 graph_parent_is_merge(struct rev_graph *graph)
3875         return graph->parents->size > 1;
3878 static inline void
3879 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
3881         struct commit *commit = graph->commit;
3883         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
3884                 commit->graph[commit->graph_size++] = symbol;
3887 static void
3888 done_rev_graph(struct rev_graph *graph)
3890         if (graph_parent_is_merge(graph) &&
3891             graph->pos < graph->size - 1 &&
3892             graph->next->size == graph->size + graph->parents->size - 1) {
3893                 size_t i = graph->pos + graph->parents->size - 1;
3895                 graph->commit->graph_size = i * 2;
3896                 while (i < graph->next->size - 1) {
3897                         append_to_rev_graph(graph, ' ');
3898                         append_to_rev_graph(graph, '\\');
3899                         i++;
3900                 }
3901         }
3903         graph->size = graph->pos = 0;
3904         graph->commit = NULL;
3905         memset(graph->parents, 0, sizeof(*graph->parents));
3908 static void
3909 push_rev_graph(struct rev_graph *graph, char *parent)
3911         int i;
3913         /* "Collapse" duplicate parents lines.
3914          *
3915          * FIXME: This needs to also update update the drawn graph but
3916          * for now it just serves as a method for pruning graph lines. */
3917         for (i = 0; i < graph->size; i++)
3918                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
3919                         return;
3921         if (graph->size < SIZEOF_REVITEMS) {
3922                 string_copy_rev(graph->rev[graph->size++], parent);
3923         }
3926 static chtype
3927 get_rev_graph_symbol(struct rev_graph *graph)
3929         chtype symbol;
3931         if (graph->parents->size == 0)
3932                 symbol = REVGRAPH_INIT;
3933         else if (graph_parent_is_merge(graph))
3934                 symbol = REVGRAPH_MERGE;
3935         else if (graph->pos >= graph->size)
3936                 symbol = REVGRAPH_BRANCH;
3937         else
3938                 symbol = REVGRAPH_COMMIT;
3940         return symbol;
3943 static void
3944 draw_rev_graph(struct rev_graph *graph)
3946         struct rev_filler {
3947                 chtype separator, line;
3948         };
3949         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
3950         static struct rev_filler fillers[] = {
3951                 { ' ',  REVGRAPH_LINE },
3952                 { '`',  '.' },
3953                 { '\'', ' ' },
3954                 { '/',  ' ' },
3955         };
3956         chtype symbol = get_rev_graph_symbol(graph);
3957         struct rev_filler *filler;
3958         size_t i;
3960         filler = &fillers[DEFAULT];
3962         for (i = 0; i < graph->pos; i++) {
3963                 append_to_rev_graph(graph, filler->line);
3964                 if (graph_parent_is_merge(graph->prev) &&
3965                     graph->prev->pos == i)
3966                         filler = &fillers[RSHARP];
3968                 append_to_rev_graph(graph, filler->separator);
3969         }
3971         /* Place the symbol for this revision. */
3972         append_to_rev_graph(graph, symbol);
3974         if (graph->prev->size > graph->size)
3975                 filler = &fillers[RDIAG];
3976         else
3977                 filler = &fillers[DEFAULT];
3979         i++;
3981         for (; i < graph->size; i++) {
3982                 append_to_rev_graph(graph, filler->separator);
3983                 append_to_rev_graph(graph, filler->line);
3984                 if (graph_parent_is_merge(graph->prev) &&
3985                     i < graph->prev->pos + graph->parents->size)
3986                         filler = &fillers[RSHARP];
3987                 if (graph->prev->size > graph->size)
3988                         filler = &fillers[LDIAG];
3989         }
3991         if (graph->prev->size > graph->size) {
3992                 append_to_rev_graph(graph, filler->separator);
3993                 if (filler->line != ' ')
3994                         append_to_rev_graph(graph, filler->line);
3995         }
3998 /* Prepare the next rev graph */
3999 static void
4000 prepare_rev_graph(struct rev_graph *graph)
4002         size_t i;
4004         /* First, traverse all lines of revisions up to the active one. */
4005         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
4006                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
4007                         break;
4009                 push_rev_graph(graph->next, graph->rev[graph->pos]);
4010         }
4012         /* Interleave the new revision parent(s). */
4013         for (i = 0; i < graph->parents->size; i++)
4014                 push_rev_graph(graph->next, graph->parents->rev[i]);
4016         /* Lastly, put any remaining revisions. */
4017         for (i = graph->pos + 1; i < graph->size; i++)
4018                 push_rev_graph(graph->next, graph->rev[i]);
4021 static void
4022 update_rev_graph(struct rev_graph *graph)
4024         /* If this is the finalizing update ... */
4025         if (graph->commit)
4026                 prepare_rev_graph(graph);
4028         /* Graph visualization needs a one rev look-ahead,
4029          * so the first update doesn't visualize anything. */
4030         if (!graph->prev->commit)
4031                 return;
4033         draw_rev_graph(graph->prev);
4034         done_rev_graph(graph->prev->prev);
4038 /*
4039  * Main view backend
4040  */
4042 static bool
4043 main_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
4045         char buf[DATE_COLS + 1];
4046         struct commit *commit = line->data;
4047         enum line_type type;
4048         int col = 0;
4049         size_t timelen;
4050         size_t authorlen;
4051         int trimmed = 1;
4053         if (!*commit->author)
4054                 return FALSE;
4056         wmove(view->win, lineno, col);
4058         if (selected) {
4059                 type = LINE_CURSOR;
4060                 wattrset(view->win, get_line_attr(type));
4061                 wchgat(view->win, -1, 0, type, NULL);
4063         } else {
4064                 type = LINE_MAIN_COMMIT;
4065                 wattrset(view->win, get_line_attr(LINE_MAIN_DATE));
4066         }
4068         timelen = strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time);
4069         waddnstr(view->win, buf, timelen);
4070         waddstr(view->win, " ");
4072         col += DATE_COLS;
4073         wmove(view->win, lineno, col);
4074         if (type != LINE_CURSOR)
4075                 wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR));
4077         if (opt_utf8) {
4078                 authorlen = utf8_length(commit->author, AUTHOR_COLS - 2, &col, &trimmed);
4079         } else {
4080                 authorlen = strlen(commit->author);
4081                 if (authorlen > AUTHOR_COLS - 2) {
4082                         authorlen = AUTHOR_COLS - 2;
4083                         trimmed = 1;
4084                 }
4085         }
4087         if (trimmed) {
4088                 waddnstr(view->win, commit->author, authorlen);
4089                 if (type != LINE_CURSOR)
4090                         wattrset(view->win, get_line_attr(LINE_MAIN_DELIM));
4091                 waddch(view->win, '~');
4092         } else {
4093                 waddstr(view->win, commit->author);
4094         }
4096         col += AUTHOR_COLS;
4097         if (type != LINE_CURSOR)
4098                 wattrset(view->win, A_NORMAL);
4100         if (opt_rev_graph && commit->graph_size) {
4101                 size_t i;
4103                 wmove(view->win, lineno, col);
4104                 /* Using waddch() instead of waddnstr() ensures that
4105                  * they'll be rendered correctly for the cursor line. */
4106                 for (i = 0; i < commit->graph_size; i++)
4107                         waddch(view->win, commit->graph[i]);
4109                 waddch(view->win, ' ');
4110                 col += commit->graph_size + 1;
4111         }
4113         wmove(view->win, lineno, col);
4115         if (commit->refs) {
4116                 size_t i = 0;
4118                 do {
4119                         if (type == LINE_CURSOR)
4120                                 ;
4121                         else if (commit->refs[i]->tag)
4122                                 wattrset(view->win, get_line_attr(LINE_MAIN_TAG));
4123                         else if (commit->refs[i]->remote)
4124                                 wattrset(view->win, get_line_attr(LINE_MAIN_REMOTE));
4125                         else
4126                                 wattrset(view->win, get_line_attr(LINE_MAIN_REF));
4127                         waddstr(view->win, "[");
4128                         waddstr(view->win, commit->refs[i]->name);
4129                         waddstr(view->win, "]");
4130                         if (type != LINE_CURSOR)
4131                                 wattrset(view->win, A_NORMAL);
4132                         waddstr(view->win, " ");
4133                         col += strlen(commit->refs[i]->name) + STRING_SIZE("[] ");
4134                 } while (commit->refs[i++]->next);
4135         }
4137         if (type != LINE_CURSOR)
4138                 wattrset(view->win, get_line_attr(type));
4140         {
4141                 int titlelen = strlen(commit->title);
4143                 if (col + titlelen > view->width)
4144                         titlelen = view->width - col;
4146                 waddnstr(view->win, commit->title, titlelen);
4147         }
4149         return TRUE;
4152 /* Reads git log --pretty=raw output and parses it into the commit struct. */
4153 static bool
4154 main_read(struct view *view, char *line)
4156         static struct rev_graph *graph = graph_stacks;
4157         enum line_type type;
4158         struct commit *commit;
4160         if (!line) {
4161                 update_rev_graph(graph);
4162                 return TRUE;
4163         }
4165         type = get_line_type(line);
4166         if (type == LINE_COMMIT) {
4167                 commit = calloc(1, sizeof(struct commit));
4168                 if (!commit)
4169                         return FALSE;
4171                 string_copy_rev(commit->id, line + STRING_SIZE("commit "));
4172                 commit->refs = get_refs(commit->id);
4173                 graph->commit = commit;
4174                 add_line_data(view, commit, LINE_MAIN_COMMIT);
4175                 return TRUE;
4176         }
4178         if (!view->lines)
4179                 return TRUE;
4180         commit = view->line[view->lines - 1].data;
4182         switch (type) {
4183         case LINE_PARENT:
4184                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
4185                 break;
4187         case LINE_AUTHOR:
4188         {
4189                 /* Parse author lines where the name may be empty:
4190                  *      author  <email@address.tld> 1138474660 +0100
4191                  */
4192                 char *ident = line + STRING_SIZE("author ");
4193                 char *nameend = strchr(ident, '<');
4194                 char *emailend = strchr(ident, '>');
4196                 if (!nameend || !emailend)
4197                         break;
4199                 update_rev_graph(graph);
4200                 graph = graph->next;
4202                 *nameend = *emailend = 0;
4203                 ident = chomp_string(ident);
4204                 if (!*ident) {
4205                         ident = chomp_string(nameend + 1);
4206                         if (!*ident)
4207                                 ident = "Unknown";
4208                 }
4210                 string_ncopy(commit->author, ident, strlen(ident));
4212                 /* Parse epoch and timezone */
4213                 if (emailend[1] == ' ') {
4214                         char *secs = emailend + 2;
4215                         char *zone = strchr(secs, ' ');
4216                         time_t time = (time_t) atol(secs);
4218                         if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
4219                                 long tz;
4221                                 zone++;
4222                                 tz  = ('0' - zone[1]) * 60 * 60 * 10;
4223                                 tz += ('0' - zone[2]) * 60 * 60;
4224                                 tz += ('0' - zone[3]) * 60;
4225                                 tz += ('0' - zone[4]) * 60;
4227                                 if (zone[0] == '-')
4228                                         tz = -tz;
4230                                 time -= tz;
4231                         }
4233                         gmtime_r(&time, &commit->time);
4234                 }
4235                 break;
4236         }
4237         default:
4238                 /* Fill in the commit title if it has not already been set. */
4239                 if (commit->title[0])
4240                         break;
4242                 /* Require titles to start with a non-space character at the
4243                  * offset used by git log. */
4244                 if (strncmp(line, "    ", 4))
4245                         break;
4246                 line += 4;
4247                 /* Well, if the title starts with a whitespace character,
4248                  * try to be forgiving.  Otherwise we end up with no title. */
4249                 while (isspace(*line))
4250                         line++;
4251                 if (*line == '\0')
4252                         break;
4253                 /* FIXME: More graceful handling of titles; append "..." to
4254                  * shortened titles, etc. */
4256                 string_ncopy(commit->title, line, strlen(line));
4257         }
4259         return TRUE;
4262 static enum request
4263 main_request(struct view *view, enum request request, struct line *line)
4265         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4267         if (request == REQ_ENTER)
4268                 open_view(view, REQ_VIEW_DIFF, flags);
4269         else
4270                 return request;
4272         return REQ_NONE;
4275 static bool
4276 main_grep(struct view *view, struct line *line)
4278         struct commit *commit = line->data;
4279         enum { S_TITLE, S_AUTHOR, S_DATE, S_END } state;
4280         char buf[DATE_COLS + 1];
4281         regmatch_t pmatch;
4283         for (state = S_TITLE; state < S_END; state++) {
4284                 char *text;
4286                 switch (state) {
4287                 case S_TITLE:   text = commit->title;   break;
4288                 case S_AUTHOR:  text = commit->author;  break;
4289                 case S_DATE:
4290                         if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
4291                                 continue;
4292                         text = buf;
4293                         break;
4295                 default:
4296                         return FALSE;
4297                 }
4299                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4300                         return TRUE;
4301         }
4303         return FALSE;
4306 static void
4307 main_select(struct view *view, struct line *line)
4309         struct commit *commit = line->data;
4311         string_copy_rev(view->ref, commit->id);
4312         string_copy_rev(ref_commit, view->ref);
4315 static struct view_ops main_ops = {
4316         "commit",
4317         NULL,
4318         main_read,
4319         main_draw,
4320         main_request,
4321         main_grep,
4322         main_select,
4323 };
4326 /*
4327  * Unicode / UTF-8 handling
4328  *
4329  * NOTE: Much of the following code for dealing with unicode is derived from
4330  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
4331  * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
4332  */
4334 /* I've (over)annotated a lot of code snippets because I am not entirely
4335  * confident that the approach taken by this small UTF-8 interface is correct.
4336  * --jonas */
4338 static inline int
4339 unicode_width(unsigned long c)
4341         if (c >= 0x1100 &&
4342            (c <= 0x115f                         /* Hangul Jamo */
4343             || c == 0x2329
4344             || c == 0x232a
4345             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
4346                                                 /* CJK ... Yi */
4347             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
4348             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
4349             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
4350             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
4351             || (c >= 0xffe0  && c <= 0xffe6)
4352             || (c >= 0x20000 && c <= 0x2fffd)
4353             || (c >= 0x30000 && c <= 0x3fffd)))
4354                 return 2;
4356         return 1;
4359 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
4360  * Illegal bytes are set one. */
4361 static const unsigned char utf8_bytes[256] = {
4362         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,
4363         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,
4364         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,
4365         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,
4366         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,
4367         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,
4368         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,
4369         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,
4370 };
4372 /* Decode UTF-8 multi-byte representation into a unicode character. */
4373 static inline unsigned long
4374 utf8_to_unicode(const char *string, size_t length)
4376         unsigned long unicode;
4378         switch (length) {
4379         case 1:
4380                 unicode  =   string[0];
4381                 break;
4382         case 2:
4383                 unicode  =  (string[0] & 0x1f) << 6;
4384                 unicode +=  (string[1] & 0x3f);
4385                 break;
4386         case 3:
4387                 unicode  =  (string[0] & 0x0f) << 12;
4388                 unicode += ((string[1] & 0x3f) << 6);
4389                 unicode +=  (string[2] & 0x3f);
4390                 break;
4391         case 4:
4392                 unicode  =  (string[0] & 0x0f) << 18;
4393                 unicode += ((string[1] & 0x3f) << 12);
4394                 unicode += ((string[2] & 0x3f) << 6);
4395                 unicode +=  (string[3] & 0x3f);
4396                 break;
4397         case 5:
4398                 unicode  =  (string[0] & 0x0f) << 24;
4399                 unicode += ((string[1] & 0x3f) << 18);
4400                 unicode += ((string[2] & 0x3f) << 12);
4401                 unicode += ((string[3] & 0x3f) << 6);
4402                 unicode +=  (string[4] & 0x3f);
4403                 break;
4404         case 6:
4405                 unicode  =  (string[0] & 0x01) << 30;
4406                 unicode += ((string[1] & 0x3f) << 24);
4407                 unicode += ((string[2] & 0x3f) << 18);
4408                 unicode += ((string[3] & 0x3f) << 12);
4409                 unicode += ((string[4] & 0x3f) << 6);
4410                 unicode +=  (string[5] & 0x3f);
4411                 break;
4412         default:
4413                 die("Invalid unicode length");
4414         }
4416         /* Invalid characters could return the special 0xfffd value but NUL
4417          * should be just as good. */
4418         return unicode > 0xffff ? 0 : unicode;
4421 /* Calculates how much of string can be shown within the given maximum width
4422  * and sets trimmed parameter to non-zero value if all of string could not be
4423  * shown.
4424  *
4425  * Additionally, adds to coloffset how many many columns to move to align with
4426  * the expected position. Takes into account how multi-byte and double-width
4427  * characters will effect the cursor position.
4428  *
4429  * Returns the number of bytes to output from string to satisfy max_width. */
4430 static size_t
4431 utf8_length(const char *string, size_t max_width, int *coloffset, int *trimmed)
4433         const char *start = string;
4434         const char *end = strchr(string, '\0');
4435         size_t mbwidth = 0;
4436         size_t width = 0;
4438         *trimmed = 0;
4440         while (string < end) {
4441                 int c = *(unsigned char *) string;
4442                 unsigned char bytes = utf8_bytes[c];
4443                 size_t ucwidth;
4444                 unsigned long unicode;
4446                 if (string + bytes > end)
4447                         break;
4449                 /* Change representation to figure out whether
4450                  * it is a single- or double-width character. */
4452                 unicode = utf8_to_unicode(string, bytes);
4453                 /* FIXME: Graceful handling of invalid unicode character. */
4454                 if (!unicode)
4455                         break;
4457                 ucwidth = unicode_width(unicode);
4458                 width  += ucwidth;
4459                 if (width > max_width) {
4460                         *trimmed = 1;
4461                         break;
4462                 }
4464                 /* The column offset collects the differences between the
4465                  * number of bytes encoding a character and the number of
4466                  * columns will be used for rendering said character.
4467                  *
4468                  * So if some character A is encoded in 2 bytes, but will be
4469                  * represented on the screen using only 1 byte this will and up
4470                  * adding 1 to the multi-byte column offset.
4471                  *
4472                  * Assumes that no double-width character can be encoding in
4473                  * less than two bytes. */
4474                 if (bytes > ucwidth)
4475                         mbwidth += bytes - ucwidth;
4477                 string  += bytes;
4478         }
4480         *coloffset += mbwidth;
4482         return string - start;
4486 /*
4487  * Status management
4488  */
4490 /* Whether or not the curses interface has been initialized. */
4491 static bool cursed = FALSE;
4493 /* The status window is used for polling keystrokes. */
4494 static WINDOW *status_win;
4496 static bool status_empty = TRUE;
4498 /* Update status and title window. */
4499 static void
4500 report(const char *msg, ...)
4502         struct view *view = display[current_view];
4504         if (input_mode)
4505                 return;
4507         if (!view) {
4508                 char buf[SIZEOF_STR];
4509                 va_list args;
4511                 va_start(args, msg);
4512                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
4513                         buf[sizeof(buf) - 1] = 0;
4514                         buf[sizeof(buf) - 2] = '.';
4515                         buf[sizeof(buf) - 3] = '.';
4516                         buf[sizeof(buf) - 4] = '.';
4517                 }
4518                 va_end(args);
4519                 die("%s", buf);
4520         }
4522         if (!status_empty || *msg) {
4523                 va_list args;
4525                 va_start(args, msg);
4527                 wmove(status_win, 0, 0);
4528                 if (*msg) {
4529                         vwprintw(status_win, msg, args);
4530                         status_empty = FALSE;
4531                 } else {
4532                         status_empty = TRUE;
4533                 }
4534                 wclrtoeol(status_win);
4535                 wrefresh(status_win);
4537                 va_end(args);
4538         }
4540         update_view_title(view);
4541         update_display_cursor(view);
4544 /* Controls when nodelay should be in effect when polling user input. */
4545 static void
4546 set_nonblocking_input(bool loading)
4548         static unsigned int loading_views;
4550         if ((loading == FALSE && loading_views-- == 1) ||
4551             (loading == TRUE  && loading_views++ == 0))
4552                 nodelay(status_win, loading);
4555 static void
4556 init_display(void)
4558         int x, y;
4560         /* Initialize the curses library */
4561         if (isatty(STDIN_FILENO)) {
4562                 cursed = !!initscr();
4563         } else {
4564                 /* Leave stdin and stdout alone when acting as a pager. */
4565                 FILE *io = fopen("/dev/tty", "r+");
4567                 if (!io)
4568                         die("Failed to open /dev/tty");
4569                 cursed = !!newterm(NULL, io, io);
4570         }
4572         if (!cursed)
4573                 die("Failed to initialize curses");
4575         nonl();         /* Tell curses not to do NL->CR/NL on output */
4576         cbreak();       /* Take input chars one at a time, no wait for \n */
4577         noecho();       /* Don't echo input */
4578         leaveok(stdscr, TRUE);
4580         if (has_colors())
4581                 init_colors();
4583         getmaxyx(stdscr, y, x);
4584         status_win = newwin(1, 0, y - 1, 0);
4585         if (!status_win)
4586                 die("Failed to create status window");
4588         /* Enable keyboard mapping */
4589         keypad(status_win, TRUE);
4590         wbkgdset(status_win, get_line_attr(LINE_STATUS));
4593 static char *
4594 read_prompt(const char *prompt)
4596         enum { READING, STOP, CANCEL } status = READING;
4597         static char buf[sizeof(opt_cmd) - STRING_SIZE("git \0")];
4598         int pos = 0;
4600         while (status == READING) {
4601                 struct view *view;
4602                 int i, key;
4604                 input_mode = TRUE;
4606                 foreach_view (view, i)
4607                         update_view(view);
4609                 input_mode = FALSE;
4611                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
4612                 wclrtoeol(status_win);
4614                 /* Refresh, accept single keystroke of input */
4615                 key = wgetch(status_win);
4616                 switch (key) {
4617                 case KEY_RETURN:
4618                 case KEY_ENTER:
4619                 case '\n':
4620                         status = pos ? STOP : CANCEL;
4621                         break;
4623                 case KEY_BACKSPACE:
4624                         if (pos > 0)
4625                                 pos--;
4626                         else
4627                                 status = CANCEL;
4628                         break;
4630                 case KEY_ESC:
4631                         status = CANCEL;
4632                         break;
4634                 case ERR:
4635                         break;
4637                 default:
4638                         if (pos >= sizeof(buf)) {
4639                                 report("Input string too long");
4640                                 return NULL;
4641                         }
4643                         if (isprint(key))
4644                                 buf[pos++] = (char) key;
4645                 }
4646         }
4648         /* Clear the status window */
4649         status_empty = FALSE;
4650         report("");
4652         if (status == CANCEL)
4653                 return NULL;
4655         buf[pos++] = 0;
4657         return buf;
4660 /*
4661  * Repository references
4662  */
4664 static struct ref *refs;
4665 static size_t refs_size;
4667 /* Id <-> ref store */
4668 static struct ref ***id_refs;
4669 static size_t id_refs_size;
4671 static struct ref **
4672 get_refs(char *id)
4674         struct ref ***tmp_id_refs;
4675         struct ref **ref_list = NULL;
4676         size_t ref_list_size = 0;
4677         size_t i;
4679         for (i = 0; i < id_refs_size; i++)
4680                 if (!strcmp(id, id_refs[i][0]->id))
4681                         return id_refs[i];
4683         tmp_id_refs = realloc(id_refs, (id_refs_size + 1) * sizeof(*id_refs));
4684         if (!tmp_id_refs)
4685                 return NULL;
4687         id_refs = tmp_id_refs;
4689         for (i = 0; i < refs_size; i++) {
4690                 struct ref **tmp;
4692                 if (strcmp(id, refs[i].id))
4693                         continue;
4695                 tmp = realloc(ref_list, (ref_list_size + 1) * sizeof(*ref_list));
4696                 if (!tmp) {
4697                         if (ref_list)
4698                                 free(ref_list);
4699                         return NULL;
4700                 }
4702                 ref_list = tmp;
4703                 if (ref_list_size > 0)
4704                         ref_list[ref_list_size - 1]->next = 1;
4705                 ref_list[ref_list_size] = &refs[i];
4707                 /* XXX: The properties of the commit chains ensures that we can
4708                  * safely modify the shared ref. The repo references will
4709                  * always be similar for the same id. */
4710                 ref_list[ref_list_size]->next = 0;
4711                 ref_list_size++;
4712         }
4714         if (ref_list)
4715                 id_refs[id_refs_size++] = ref_list;
4717         return ref_list;
4720 static int
4721 read_ref(char *id, size_t idlen, char *name, size_t namelen)
4723         struct ref *ref;
4724         bool tag = FALSE;
4725         bool remote = FALSE;
4727         if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
4728                 /* Commits referenced by tags has "^{}" appended. */
4729                 if (name[namelen - 1] != '}')
4730                         return OK;
4732                 while (namelen > 0 && name[namelen] != '^')
4733                         namelen--;
4735                 tag = TRUE;
4736                 namelen -= STRING_SIZE("refs/tags/");
4737                 name    += STRING_SIZE("refs/tags/");
4739         } else if (!strncmp(name, "refs/remotes/", STRING_SIZE("refs/remotes/"))) {
4740                 remote = TRUE;
4741                 namelen -= STRING_SIZE("refs/remotes/");
4742                 name    += STRING_SIZE("refs/remotes/");
4744         } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
4745                 namelen -= STRING_SIZE("refs/heads/");
4746                 name    += STRING_SIZE("refs/heads/");
4748         } else if (!strcmp(name, "HEAD")) {
4749                 return OK;
4750         }
4752         refs = realloc(refs, sizeof(*refs) * (refs_size + 1));
4753         if (!refs)
4754                 return ERR;
4756         ref = &refs[refs_size++];
4757         ref->name = malloc(namelen + 1);
4758         if (!ref->name)
4759                 return ERR;
4761         strncpy(ref->name, name, namelen);
4762         ref->name[namelen] = 0;
4763         ref->tag = tag;
4764         ref->remote = remote;
4765         string_copy_rev(ref->id, id);
4767         return OK;
4770 static int
4771 load_refs(void)
4773         const char *cmd_env = getenv("TIG_LS_REMOTE");
4774         const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
4776         return read_properties(popen(cmd, "r"), "\t", read_ref);
4779 static int
4780 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
4782         if (!strcmp(name, "i18n.commitencoding"))
4783                 string_ncopy(opt_encoding, value, valuelen);
4785         if (!strcmp(name, "core.editor"))
4786                 string_ncopy(opt_editor, value, valuelen);
4788         return OK;
4791 static int
4792 load_repo_config(void)
4794         return read_properties(popen(GIT_CONFIG " --list", "r"),
4795                                "=", read_repo_config_option);
4798 static int
4799 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
4801         if (!opt_git_dir[0]) {
4802                 string_ncopy(opt_git_dir, name, namelen);
4804         } else if (opt_is_inside_work_tree == -1) {
4805                 /* This can be 3 different values depending on the
4806                  * version of git being used. If git-rev-parse does not
4807                  * understand --is-inside-work-tree it will simply echo
4808                  * the option else either "true" or "false" is printed.
4809                  * Default to true for the unknown case. */
4810                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
4812         } else {
4813                 string_ncopy(opt_cdup, name, namelen);
4814         }
4816         return OK;
4819 /* XXX: The line outputted by "--show-cdup" can be empty so the option
4820  * must be the last one! */
4821 static int
4822 load_repo_info(void)
4824         return read_properties(popen("git rev-parse --git-dir --is-inside-work-tree --show-cdup 2>/dev/null", "r"),
4825                                "=", read_repo_info);
4828 static int
4829 read_properties(FILE *pipe, const char *separators,
4830                 int (*read_property)(char *, size_t, char *, size_t))
4832         char buffer[BUFSIZ];
4833         char *name;
4834         int state = OK;
4836         if (!pipe)
4837                 return ERR;
4839         while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
4840                 char *value;
4841                 size_t namelen;
4842                 size_t valuelen;
4844                 name = chomp_string(name);
4845                 namelen = strcspn(name, separators);
4847                 if (name[namelen]) {
4848                         name[namelen] = 0;
4849                         value = chomp_string(name + namelen + 1);
4850                         valuelen = strlen(value);
4852                 } else {
4853                         value = "";
4854                         valuelen = 0;
4855                 }
4857                 state = read_property(name, namelen, value, valuelen);
4858         }
4860         if (state != ERR && ferror(pipe))
4861                 state = ERR;
4863         pclose(pipe);
4865         return state;
4869 /*
4870  * Main
4871  */
4873 static void __NORETURN
4874 quit(int sig)
4876         /* XXX: Restore tty modes and let the OS cleanup the rest! */
4877         if (cursed)
4878                 endwin();
4879         exit(0);
4882 static void __NORETURN
4883 die(const char *err, ...)
4885         va_list args;
4887         endwin();
4889         va_start(args, err);
4890         fputs("tig: ", stderr);
4891         vfprintf(stderr, err, args);
4892         fputs("\n", stderr);
4893         va_end(args);
4895         exit(1);
4898 int
4899 main(int argc, char *argv[])
4901         struct view *view;
4902         enum request request;
4903         size_t i;
4905         signal(SIGINT, quit);
4907         if (setlocale(LC_ALL, "")) {
4908                 char *codeset = nl_langinfo(CODESET);
4910                 string_ncopy(opt_codeset, codeset, strlen(codeset));
4911         }
4913         if (load_repo_info() == ERR)
4914                 die("Failed to load repo info.");
4916         if (load_options() == ERR)
4917                 die("Failed to load user config.");
4919         /* Load the repo config file so options can be overwritten from
4920          * the command line. */
4921         if (load_repo_config() == ERR)
4922                 die("Failed to load repo config.");
4924         if (!parse_options(argc, argv))
4925                 return 0;
4927         /* Require a git repository unless when running in pager mode. */
4928         if (!opt_git_dir[0])
4929                 die("Not a git repository");
4931         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
4932                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
4933                 if (opt_iconv == ICONV_NONE)
4934                         die("Failed to initialize character set conversion");
4935         }
4937         if (load_refs() == ERR)
4938                 die("Failed to load refs.");
4940         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
4941                 view->cmd_env = getenv(view->cmd_env);
4943         request = opt_request;
4945         init_display();
4947         while (view_driver(display[current_view], request)) {
4948                 int key;
4949                 int i;
4951                 foreach_view (view, i)
4952                         update_view(view);
4954                 /* Refresh, accept single keystroke of input */
4955                 key = wgetch(status_win);
4957                 /* wgetch() with nodelay() enabled returns ERR when there's no
4958                  * input. */
4959                 if (key == ERR) {
4960                         request = REQ_NONE;
4961                         continue;
4962                 }
4964                 request = get_keybinding(display[current_view]->keymap, key);
4966                 /* Some low-level request handling. This keeps access to
4967                  * status_win restricted. */
4968                 switch (request) {
4969                 case REQ_PROMPT:
4970                 {
4971                         char *cmd = read_prompt(":");
4973                         if (cmd && string_format(opt_cmd, "git %s", cmd)) {
4974                                 if (strncmp(cmd, "show", 4) && isspace(cmd[4])) {
4975                                         opt_request = REQ_VIEW_DIFF;
4976                                 } else {
4977                                         opt_request = REQ_VIEW_PAGER;
4978                                 }
4979                                 break;
4980                         }
4982                         request = REQ_NONE;
4983                         break;
4984                 }
4985                 case REQ_SEARCH:
4986                 case REQ_SEARCH_BACK:
4987                 {
4988                         const char *prompt = request == REQ_SEARCH
4989                                            ? "/" : "?";
4990                         char *search = read_prompt(prompt);
4992                         if (search)
4993                                 string_ncopy(opt_search, search, strlen(search));
4994                         else
4995                                 request = REQ_NONE;
4996                         break;
4997                 }
4998                 case REQ_SCREEN_RESIZE:
4999                 {
5000                         int height, width;
5002                         getmaxyx(stdscr, height, width);
5004                         /* Resize the status view and let the view driver take
5005                          * care of resizing the displayed views. */
5006                         wresize(status_win, 1, width);
5007                         mvwin(status_win, height - 1, 0);
5008                         wrefresh(status_win);
5009                         break;
5010                 }
5011                 default:
5012                         break;
5013                 }
5014         }
5016         quit(0);
5018         return 0;