New actions toggle-date, toggle-author, and toggle-refs.
[tig] / 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  */
13
14 #ifdef HAVE_CONFIG_H
15 #include "config.h"
16 #endif
17
18 #ifndef TIG_VERSION
19 #define TIG_VERSION "unknown-version"
20 #endif
21
22 #ifndef DEBUG
23 #define NDEBUG
24 #endif
25
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>
38
39 #include <regex.h>
40
41 #include <locale.h>
42 #include <langinfo.h>
43 #include <iconv.h>
44
45 /* ncurses(3): Must be defined to have extended wide-character functions. */
46 #define _XOPEN_SOURCE_EXTENDED
47
48 #include <curses.h>
49
50 #if __GNUC__ >= 3
51 #define __NORETURN __attribute__((__noreturn__))
52 #else
53 #define __NORETURN
54 #endif
55
56 static void __NORETURN die(const char *err, ...);
57 static void warn(const char *msg, ...);
58 static void report(const char *msg, ...);
59 static int read_properties(FILE *pipe, const char *separators, int (*read)(char *, size_t, char *, size_t));
60 static void set_nonblocking_input(bool loading);
61 static size_t utf8_length(const char *string, size_t max_width, int *trimmed, bool reserve);
62
63 #define ABS(x)          ((x) >= 0  ? (x) : -(x))
64 #define MIN(x, y)       ((x) < (y) ? (x) :  (y))
65
66 #define ARRAY_SIZE(x)   (sizeof(x) / sizeof(x[0]))
67 #define STRING_SIZE(x)  (sizeof(x) - 1)
68
69 #define SIZEOF_STR      1024    /* Default string size. */
70 #define SIZEOF_REF      256     /* Size of symbolic or SHA1 ID. */
71 #define SIZEOF_REV      41      /* Holds a SHA-1 and an ending NUL */
72
73 /* Revision graph */
74
75 #define REVGRAPH_INIT   'I'
76 #define REVGRAPH_MERGE  'M'
77 #define REVGRAPH_BRANCH '+'
78 #define REVGRAPH_COMMIT '*'
79 #define REVGRAPH_BOUND  '^'
80 #define REVGRAPH_LINE   '|'
81
82 #define SIZEOF_REVGRAPH 19      /* Size of revision ancestry graphics. */
83
84 /* This color name can be used to refer to the default term colors. */
85 #define COLOR_DEFAULT   (-1)
86
87 #define ICONV_NONE      ((iconv_t) -1)
88 #ifndef ICONV_CONST
89 #define ICONV_CONST     /* nothing */
90 #endif
91
92 /* The format and size of the date column in the main view. */
93 #define DATE_FORMAT     "%Y-%m-%d %H:%M"
94 #define DATE_COLS       STRING_SIZE("2006-04-29 14:21 ")
95
96 #define AUTHOR_COLS     20
97
98 /* The default interval between line numbers. */
99 #define NUMBER_INTERVAL 1
100
101 #define TABSIZE         8
102
103 #define SCALE_SPLIT_VIEW(height)        ((height) * 2 / 3)
104
105 #ifndef GIT_CONFIG
106 #define GIT_CONFIG "git config"
107 #endif
108
109 #define TIG_LS_REMOTE \
110         "git ls-remote $(git rev-parse --git-dir) 2>/dev/null"
111
112 #define TIG_DIFF_CMD \
113         "git show --pretty=fuller --no-color --root --patch-with-stat --find-copies-harder -C %s 2>/dev/null"
114
115 #define TIG_LOG_CMD     \
116         "git log --no-color --cc --stat -n100 %s 2>/dev/null"
117
118 #define TIG_MAIN_CMD \
119         "git log --no-color --topo-order --boundary --pretty=raw %s 2>/dev/null"
120
121 #define TIG_TREE_CMD    \
122         "git ls-tree %s %s"
123
124 #define TIG_BLOB_CMD    \
125         "git cat-file blob %s"
126
127 /* XXX: Needs to be defined to the empty string. */
128 #define TIG_HELP_CMD    ""
129 #define TIG_PAGER_CMD   ""
130 #define TIG_STATUS_CMD  ""
131 #define TIG_STAGE_CMD   ""
132
133 /* Some ascii-shorthands fitted into the ncurses namespace. */
134 #define KEY_TAB         '\t'
135 #define KEY_RETURN      '\r'
136 #define KEY_ESC         27
137
138
139 struct ref {
140         char *name;             /* Ref name; tag or head names are shortened. */
141         char id[SIZEOF_REV];    /* Commit SHA1 ID */
142         unsigned int tag:1;     /* Is it a tag? */
143         unsigned int remote:1;  /* Is it a remote ref? */
144         unsigned int next:1;    /* For ref lists: are there more refs? */
145 };
146
147 static struct ref **get_refs(char *id);
148
149 struct int_map {
150         const char *name;
151         int namelen;
152         int value;
153 };
154
155 static int
156 set_from_int_map(struct int_map *map, size_t map_size,
157                  int *value, const char *name, int namelen)
158 {
159
160         int i;
161
162         for (i = 0; i < map_size; i++)
163                 if (namelen == map[i].namelen &&
164                     !strncasecmp(name, map[i].name, namelen)) {
165                         *value = map[i].value;
166                         return OK;
167                 }
168
169         return ERR;
170 }
171
172
173 /*
174  * String helpers
175  */
176
177 static inline void
178 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
179 {
180         if (srclen > dstlen - 1)
181                 srclen = dstlen - 1;
182
183         strncpy(dst, src, srclen);
184         dst[srclen] = 0;
185 }
186
187 /* Shorthands for safely copying into a fixed buffer. */
188
189 #define string_copy(dst, src) \
190         string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
191
192 #define string_ncopy(dst, src, srclen) \
193         string_ncopy_do(dst, sizeof(dst), src, srclen)
194
195 #define string_copy_rev(dst, src) \
196         string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
197
198 #define string_add(dst, from, src) \
199         string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
200
201 static char *
202 chomp_string(char *name)
203 {
204         int namelen;
205
206         while (isspace(*name))
207                 name++;
208
209         namelen = strlen(name) - 1;
210         while (namelen > 0 && isspace(name[namelen]))
211                 name[namelen--] = 0;
212
213         return name;
214 }
215
216 static bool
217 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
218 {
219         va_list args;
220         size_t pos = bufpos ? *bufpos : 0;
221
222         va_start(args, fmt);
223         pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
224         va_end(args);
225
226         if (bufpos)
227                 *bufpos = pos;
228
229         return pos >= bufsize ? FALSE : TRUE;
230 }
231
232 #define string_format(buf, fmt, args...) \
233         string_nformat(buf, sizeof(buf), NULL, fmt, args)
234
235 #define string_format_from(buf, from, fmt, args...) \
236         string_nformat(buf, sizeof(buf), from, fmt, args)
237
238 static int
239 string_enum_compare(const char *str1, const char *str2, int len)
240 {
241         size_t i;
242
243 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
244
245         /* Diff-Header == DIFF_HEADER */
246         for (i = 0; i < len; i++) {
247                 if (toupper(str1[i]) == toupper(str2[i]))
248                         continue;
249
250                 if (string_enum_sep(str1[i]) &&
251                     string_enum_sep(str2[i]))
252                         continue;
253
254                 return str1[i] - str2[i];
255         }
256
257         return 0;
258 }
259
260 /* Shell quoting
261  *
262  * NOTE: The following is a slightly modified copy of the git project's shell
263  * quoting routines found in the quote.c file.
264  *
265  * Help to copy the thing properly quoted for the shell safety.  any single
266  * quote is replaced with '\'', any exclamation point is replaced with '\!',
267  * and the whole thing is enclosed in a
268  *
269  * E.g.
270  *  original     sq_quote     result
271  *  name     ==> name      ==> 'name'
272  *  a b      ==> a b       ==> 'a b'
273  *  a'b      ==> a'\''b    ==> 'a'\''b'
274  *  a!b      ==> a'\!'b    ==> 'a'\!'b'
275  */
276
277 static size_t
278 sq_quote(char buf[SIZEOF_STR], size_t bufsize, const char *src)
279 {
280         char c;
281
282 #define BUFPUT(x) do { if (bufsize < SIZEOF_STR) buf[bufsize++] = (x); } while (0)
283
284         BUFPUT('\'');
285         while ((c = *src++)) {
286                 if (c == '\'' || c == '!') {
287                         BUFPUT('\'');
288                         BUFPUT('\\');
289                         BUFPUT(c);
290                         BUFPUT('\'');
291                 } else {
292                         BUFPUT(c);
293                 }
294         }
295         BUFPUT('\'');
296
297         if (bufsize < SIZEOF_STR)
298                 buf[bufsize] = 0;
299
300         return bufsize;
301 }
302
303
304 /*
305  * User requests
306  */
307
308 #define REQ_INFO \
309         /* XXX: Keep the view request first and in sync with views[]. */ \
310         REQ_GROUP("View switching") \
311         REQ_(VIEW_MAIN,         "Show main view"), \
312         REQ_(VIEW_DIFF,         "Show diff view"), \
313         REQ_(VIEW_LOG,          "Show log view"), \
314         REQ_(VIEW_TREE,         "Show tree view"), \
315         REQ_(VIEW_BLOB,         "Show blob view"), \
316         REQ_(VIEW_HELP,         "Show help page"), \
317         REQ_(VIEW_PAGER,        "Show pager view"), \
318         REQ_(VIEW_STATUS,       "Show status view"), \
319         REQ_(VIEW_STAGE,        "Show stage view"), \
320         \
321         REQ_GROUP("View manipulation") \
322         REQ_(ENTER,             "Enter current line and scroll"), \
323         REQ_(NEXT,              "Move to next"), \
324         REQ_(PREVIOUS,          "Move to previous"), \
325         REQ_(VIEW_NEXT,         "Move focus to next view"), \
326         REQ_(REFRESH,           "Reload and refresh"), \
327         REQ_(VIEW_CLOSE,        "Close the current view"), \
328         REQ_(QUIT,              "Close all views and quit"), \
329         \
330         REQ_GROUP("Cursor navigation") \
331         REQ_(MOVE_UP,           "Move cursor one line up"), \
332         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
333         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
334         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
335         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
336         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
337         \
338         REQ_GROUP("Scrolling") \
339         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
340         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
341         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
342         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
343         \
344         REQ_GROUP("Searching") \
345         REQ_(SEARCH,            "Search the view"), \
346         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
347         REQ_(FIND_NEXT,         "Find next search match"), \
348         REQ_(FIND_PREV,         "Find previous search match"), \
349         \
350         REQ_GROUP("Misc") \
351         REQ_(PROMPT,            "Bring up the prompt"), \
352         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
353         REQ_(SCREEN_RESIZE,     "Resize the screen"), \
354         REQ_(SHOW_VERSION,      "Show version information"), \
355         REQ_(STOP_LOADING,      "Stop all loading views"), \
356         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
357         REQ_(TOGGLE_DATE,       "Toggle date display"), \
358         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
359         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
360         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
361         REQ_(STATUS_UPDATE,     "Update file status"), \
362         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
363         REQ_(TREE_PARENT,       "Switch to parent directory in tree view"), \
364         REQ_(EDIT,              "Open in editor"), \
365         REQ_(NONE,              "Do nothing")
366
367
368 /* User action requests. */
369 enum request {
370 #define REQ_GROUP(help)
371 #define REQ_(req, help) REQ_##req
372
373         /* Offset all requests to avoid conflicts with ncurses getch values. */
374         REQ_OFFSET = KEY_MAX + 1,
375         REQ_INFO
376
377 #undef  REQ_GROUP
378 #undef  REQ_
379 };
380
381 struct request_info {
382         enum request request;
383         char *name;
384         int namelen;
385         char *help;
386 };
387
388 static struct request_info req_info[] = {
389 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
390 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
391         REQ_INFO
392 #undef  REQ_GROUP
393 #undef  REQ_
394 };
395
396 static enum request
397 get_request(const char *name)
398 {
399         int namelen = strlen(name);
400         int i;
401
402         for (i = 0; i < ARRAY_SIZE(req_info); i++)
403                 if (req_info[i].namelen == namelen &&
404                     !string_enum_compare(req_info[i].name, name, namelen))
405                         return req_info[i].request;
406
407         return REQ_NONE;
408 }
409
410
411 /*
412  * Options
413  */
414
415 static const char usage[] =
416 "tig " TIG_VERSION " (" __DATE__ ")\n"
417 "\n"
418 "Usage: tig        [options] [revs] [--] [paths]\n"
419 "   or: tig show   [options] [revs] [--] [paths]\n"
420 "   or: tig status\n"
421 "   or: tig <      [git command output]\n"
422 "\n"
423 "Options:\n"
424 "  -v, --version   Show version and exit\n"
425 "  -h, --help      Show help message and exit\n";
426
427 /* Option and state variables. */
428 static bool opt_date                    = TRUE;
429 static bool opt_author                  = TRUE;
430 static bool opt_line_number             = FALSE;
431 static bool opt_rev_graph               = FALSE;
432 static bool opt_show_refs               = TRUE;
433 static int opt_num_interval             = NUMBER_INTERVAL;
434 static int opt_tab_size                 = TABSIZE;
435 static enum request opt_request         = REQ_VIEW_MAIN;
436 static char opt_cmd[SIZEOF_STR]         = "";
437 static char opt_path[SIZEOF_STR]        = "";
438 static FILE *opt_pipe                   = NULL;
439 static char opt_encoding[20]            = "UTF-8";
440 static bool opt_utf8                    = TRUE;
441 static char opt_codeset[20]             = "UTF-8";
442 static iconv_t opt_iconv                = ICONV_NONE;
443 static char opt_search[SIZEOF_STR]      = "";
444 static char opt_cdup[SIZEOF_STR]        = "";
445 static char opt_git_dir[SIZEOF_STR]     = "";
446 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
447 static char opt_editor[SIZEOF_STR]      = "";
448
449 enum option_type {
450         OPT_NONE,
451         OPT_INT,
452 };
453
454 static bool
455 check_option(char *opt, char short_name, char *name, enum option_type type, ...)
456 {
457         va_list args;
458         char *value = "";
459         int *number;
460
461         if (opt[0] != '-')
462                 return FALSE;
463
464         if (opt[1] == '-') {
465                 int namelen = strlen(name);
466
467                 opt += 2;
468
469                 if (strncmp(opt, name, namelen))
470                         return FALSE;
471
472                 if (opt[namelen] == '=')
473                         value = opt + namelen + 1;
474
475         } else {
476                 if (!short_name || opt[1] != short_name)
477                         return FALSE;
478                 value = opt + 2;
479         }
480
481         va_start(args, type);
482         if (type == OPT_INT) {
483                 number = va_arg(args, int *);
484                 if (isdigit(*value))
485                         *number = atoi(value);
486         }
487         va_end(args);
488
489         return TRUE;
490 }
491
492 /* Returns the index of log or diff command or -1 to exit. */
493 static bool
494 parse_options(int argc, char *argv[])
495 {
496         char *altargv[1024];
497         int altargc = 0;
498         char *subcommand = NULL;
499         int i;
500
501         for (i = 1; i < argc; i++) {
502                 char *opt = argv[i];
503
504                 if (!strcmp(opt, "log") ||
505                     !strcmp(opt, "diff")) {
506                         subcommand = opt;
507                         opt_request = opt[0] == 'l'
508                                     ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
509                         warn("`tig %s' has been deprecated", opt);
510                         break;
511                 }
512
513                 if (!strcmp(opt, "show")) {
514                         subcommand = opt;
515                         opt_request = REQ_VIEW_DIFF;
516                         break;
517                 }
518
519                 if (!strcmp(opt, "status")) {
520                         subcommand = opt;
521                         opt_request = REQ_VIEW_STATUS;
522                         break;
523                 }
524
525                 if (opt[0] && opt[0] != '-')
526                         break;
527
528                 if (!strcmp(opt, "--")) {
529                         i++;
530                         break;
531                 }
532
533                 if (check_option(opt, 'v', "version", OPT_NONE)) {
534                         printf("tig version %s\n", TIG_VERSION);
535                         return FALSE;
536                 }
537
538                 if (check_option(opt, 'h', "help", OPT_NONE)) {
539                         printf(usage);
540                         return FALSE;
541                 }
542
543                 if (!strcmp(opt, "-S")) {
544                         warn("`%s' has been deprecated; use `tig status' instead", opt);
545                         opt_request = REQ_VIEW_STATUS;
546                         continue;
547                 }
548
549                 if (!strcmp(opt, "-l")) {
550                         opt_request = REQ_VIEW_LOG;
551                 } else if (!strcmp(opt, "-d")) {
552                         opt_request = REQ_VIEW_DIFF;
553                 } else if (check_option(opt, 'n', "line-number", OPT_INT, &opt_num_interval)) {
554                         opt_line_number = TRUE;
555                 } else if (check_option(opt, 'b', "tab-size", OPT_INT, &opt_tab_size)) {
556                         opt_tab_size = MIN(opt_tab_size, TABSIZE);
557                 } else {
558                         if (altargc >= ARRAY_SIZE(altargv))
559                                 die("maximum number of arguments exceeded");
560                         altargv[altargc++] = opt;
561                         continue;
562                 }
563
564                 warn("`%s' has been deprecated", opt);
565         }
566
567         /* Check that no 'alt' arguments occured before a subcommand. */
568         if (subcommand && i < argc && altargc > 0)
569                 die("unknown arguments before `%s'", argv[i]);
570
571         if (!isatty(STDIN_FILENO)) {
572                 opt_request = REQ_VIEW_PAGER;
573                 opt_pipe = stdin;
574
575         } else if (opt_request == REQ_VIEW_STATUS) {
576                 if (argc - i > 1)
577                         warn("ignoring arguments after `%s'", argv[i]);
578
579         } else if (i < argc || altargc > 0) {
580                 int alti = 0;
581                 size_t buf_size;
582
583                 if (opt_request == REQ_VIEW_MAIN)
584                         /* XXX: This is vulnerable to the user overriding
585                          * options required for the main view parser. */
586                         string_copy(opt_cmd, "git log --no-color --pretty=raw --boundary");
587                 else
588                         string_copy(opt_cmd, "git");
589                 buf_size = strlen(opt_cmd);
590
591                 while (buf_size < sizeof(opt_cmd) && alti < altargc) {
592                         opt_cmd[buf_size++] = ' ';
593                         buf_size = sq_quote(opt_cmd, buf_size, altargv[alti++]);
594                 }
595
596                 while (buf_size < sizeof(opt_cmd) && i < argc) {
597                         opt_cmd[buf_size++] = ' ';
598                         buf_size = sq_quote(opt_cmd, buf_size, argv[i++]);
599                 }
600
601                 if (buf_size >= sizeof(opt_cmd))
602                         die("command too long");
603
604                 opt_cmd[buf_size] = 0;
605         }
606
607         if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
608                 opt_utf8 = FALSE;
609
610         return TRUE;
611 }
612
613
614 /*
615  * Line-oriented content detection.
616  */
617
618 #define LINE_INFO \
619 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
620 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
621 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
622 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
623 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
624 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
625 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
626 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
627 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
628 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
629 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
630 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
631 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
632 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
633 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
634 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
635 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
636 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
637 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
638 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
639 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
640 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
641 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
642 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
643 LINE(AUTHOR,       "author ",           COLOR_CYAN,     COLOR_DEFAULT,  0), \
644 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
645 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
646 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
647 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
648 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
649 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
650 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
651 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
652 LINE(MAIN_DATE,    "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
653 LINE(MAIN_AUTHOR,  "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
654 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
655 LINE(MAIN_DELIM,   "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
656 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
657 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
658 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
659 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
660 LINE(TREE_DIR,     "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
661 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
662 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
663 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
664 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
665 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
666 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0)
667
668 enum line_type {
669 #define LINE(type, line, fg, bg, attr) \
670         LINE_##type
671         LINE_INFO
672 #undef  LINE
673 };
674
675 struct line_info {
676         const char *name;       /* Option name. */
677         int namelen;            /* Size of option name. */
678         const char *line;       /* The start of line to match. */
679         int linelen;            /* Size of string to match. */
680         int fg, bg, attr;       /* Color and text attributes for the lines. */
681 };
682
683 static struct line_info line_info[] = {
684 #define LINE(type, line, fg, bg, attr) \
685         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
686         LINE_INFO
687 #undef  LINE
688 };
689
690 static enum line_type
691 get_line_type(char *line)
692 {
693         int linelen = strlen(line);
694         enum line_type type;
695
696         for (type = 0; type < ARRAY_SIZE(line_info); type++)
697                 /* Case insensitive search matches Signed-off-by lines better. */
698                 if (linelen >= line_info[type].linelen &&
699                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
700                         return type;
701
702         return LINE_DEFAULT;
703 }
704
705 static inline int
706 get_line_attr(enum line_type type)
707 {
708         assert(type < ARRAY_SIZE(line_info));
709         return COLOR_PAIR(type) | line_info[type].attr;
710 }
711
712 static struct line_info *
713 get_line_info(char *name, int namelen)
714 {
715         enum line_type type;
716
717         for (type = 0; type < ARRAY_SIZE(line_info); type++)
718                 if (namelen == line_info[type].namelen &&
719                     !string_enum_compare(line_info[type].name, name, namelen))
720                         return &line_info[type];
721
722         return NULL;
723 }
724
725 static void
726 init_colors(void)
727 {
728         int default_bg = line_info[LINE_DEFAULT].bg;
729         int default_fg = line_info[LINE_DEFAULT].fg;
730         enum line_type type;
731
732         start_color();
733
734         if (assume_default_colors(default_fg, default_bg) == ERR) {
735                 default_bg = COLOR_BLACK;
736                 default_fg = COLOR_WHITE;
737         }
738
739         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
740                 struct line_info *info = &line_info[type];
741                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
742                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
743
744                 init_pair(type, fg, bg);
745         }
746 }
747
748 struct line {
749         enum line_type type;
750
751         /* State flags */
752         unsigned int selected:1;
753
754         void *data;             /* User data */
755 };
756
757
758 /*
759  * Keys
760  */
761
762 struct keybinding {
763         int alias;
764         enum request request;
765         struct keybinding *next;
766 };
767
768 static struct keybinding default_keybindings[] = {
769         /* View switching */
770         { 'm',          REQ_VIEW_MAIN },
771         { 'd',          REQ_VIEW_DIFF },
772         { 'l',          REQ_VIEW_LOG },
773         { 't',          REQ_VIEW_TREE },
774         { 'f',          REQ_VIEW_BLOB },
775         { 'p',          REQ_VIEW_PAGER },
776         { 'h',          REQ_VIEW_HELP },
777         { 'S',          REQ_VIEW_STATUS },
778         { 'c',          REQ_VIEW_STAGE },
779
780         /* View manipulation */
781         { 'q',          REQ_VIEW_CLOSE },
782         { KEY_TAB,      REQ_VIEW_NEXT },
783         { KEY_RETURN,   REQ_ENTER },
784         { KEY_UP,       REQ_PREVIOUS },
785         { KEY_DOWN,     REQ_NEXT },
786         { 'R',          REQ_REFRESH },
787
788         /* Cursor navigation */
789         { 'k',          REQ_MOVE_UP },
790         { 'j',          REQ_MOVE_DOWN },
791         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
792         { KEY_END,      REQ_MOVE_LAST_LINE },
793         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
794         { ' ',          REQ_MOVE_PAGE_DOWN },
795         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
796         { 'b',          REQ_MOVE_PAGE_UP },
797         { '-',          REQ_MOVE_PAGE_UP },
798
799         /* Scrolling */
800         { KEY_IC,       REQ_SCROLL_LINE_UP },
801         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
802         { 'w',          REQ_SCROLL_PAGE_UP },
803         { 's',          REQ_SCROLL_PAGE_DOWN },
804
805         /* Searching */
806         { '/',          REQ_SEARCH },
807         { '?',          REQ_SEARCH_BACK },
808         { 'n',          REQ_FIND_NEXT },
809         { 'N',          REQ_FIND_PREV },
810
811         /* Misc */
812         { 'Q',          REQ_QUIT },
813         { 'z',          REQ_STOP_LOADING },
814         { 'v',          REQ_SHOW_VERSION },
815         { 'r',          REQ_SCREEN_REDRAW },
816         { '.',          REQ_TOGGLE_LINENO },
817         { 'D',          REQ_TOGGLE_DATE },
818         { 'A',          REQ_TOGGLE_AUTHOR },
819         { 'g',          REQ_TOGGLE_REV_GRAPH },
820         { 'F',          REQ_TOGGLE_REFS },
821         { ':',          REQ_PROMPT },
822         { 'u',          REQ_STATUS_UPDATE },
823         { 'M',          REQ_STATUS_MERGE },
824         { ',',          REQ_TREE_PARENT },
825         { 'e',          REQ_EDIT },
826
827         /* Using the ncurses SIGWINCH handler. */
828         { KEY_RESIZE,   REQ_SCREEN_RESIZE },
829 };
830
831 #define KEYMAP_INFO \
832         KEYMAP_(GENERIC), \
833         KEYMAP_(MAIN), \
834         KEYMAP_(DIFF), \
835         KEYMAP_(LOG), \
836         KEYMAP_(TREE), \
837         KEYMAP_(BLOB), \
838         KEYMAP_(PAGER), \
839         KEYMAP_(HELP), \
840         KEYMAP_(STATUS), \
841         KEYMAP_(STAGE)
842
843 enum keymap {
844 #define KEYMAP_(name) KEYMAP_##name
845         KEYMAP_INFO
846 #undef  KEYMAP_
847 };
848
849 static struct int_map keymap_table[] = {
850 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
851         KEYMAP_INFO
852 #undef  KEYMAP_
853 };
854
855 #define set_keymap(map, name) \
856         set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
857
858 static struct keybinding *keybindings[ARRAY_SIZE(keymap_table)];
859
860 static void
861 add_keybinding(enum keymap keymap, enum request request, int key)
862 {
863         struct keybinding *keybinding;
864
865         keybinding = calloc(1, sizeof(*keybinding));
866         if (!keybinding)
867                 die("Failed to allocate keybinding");
868
869         keybinding->alias = key;
870         keybinding->request = request;
871         keybinding->next = keybindings[keymap];
872         keybindings[keymap] = keybinding;
873 }
874
875 /* Looks for a key binding first in the given map, then in the generic map, and
876  * lastly in the default keybindings. */
877 static enum request
878 get_keybinding(enum keymap keymap, int key)
879 {
880         struct keybinding *kbd;
881         int i;
882
883         for (kbd = keybindings[keymap]; kbd; kbd = kbd->next)
884                 if (kbd->alias == key)
885                         return kbd->request;
886
887         for (kbd = keybindings[KEYMAP_GENERIC]; kbd; kbd = kbd->next)
888                 if (kbd->alias == key)
889                         return kbd->request;
890
891         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
892                 if (default_keybindings[i].alias == key)
893                         return default_keybindings[i].request;
894
895         return (enum request) key;
896 }
897
898
899 struct key {
900         char *name;
901         int value;
902 };
903
904 static struct key key_table[] = {
905         { "Enter",      KEY_RETURN },
906         { "Space",      ' ' },
907         { "Backspace",  KEY_BACKSPACE },
908         { "Tab",        KEY_TAB },
909         { "Escape",     KEY_ESC },
910         { "Left",       KEY_LEFT },
911         { "Right",      KEY_RIGHT },
912         { "Up",         KEY_UP },
913         { "Down",       KEY_DOWN },
914         { "Insert",     KEY_IC },
915         { "Delete",     KEY_DC },
916         { "Hash",       '#' },
917         { "Home",       KEY_HOME },
918         { "End",        KEY_END },
919         { "PageUp",     KEY_PPAGE },
920         { "PageDown",   KEY_NPAGE },
921         { "F1",         KEY_F(1) },
922         { "F2",         KEY_F(2) },
923         { "F3",         KEY_F(3) },
924         { "F4",         KEY_F(4) },
925         { "F5",         KEY_F(5) },
926         { "F6",         KEY_F(6) },
927         { "F7",         KEY_F(7) },
928         { "F8",         KEY_F(8) },
929         { "F9",         KEY_F(9) },
930         { "F10",        KEY_F(10) },
931         { "F11",        KEY_F(11) },
932         { "F12",        KEY_F(12) },
933 };
934
935 static int
936 get_key_value(const char *name)
937 {
938         int i;
939
940         for (i = 0; i < ARRAY_SIZE(key_table); i++)
941                 if (!strcasecmp(key_table[i].name, name))
942                         return key_table[i].value;
943
944         if (strlen(name) == 1 && isprint(*name))
945                 return (int) *name;
946
947         return ERR;
948 }
949
950 static char *
951 get_key_name(int key_value)
952 {
953         static char key_char[] = "'X'";
954         char *seq = NULL;
955         int key;
956
957         for (key = 0; key < ARRAY_SIZE(key_table); key++)
958                 if (key_table[key].value == key_value)
959                         seq = key_table[key].name;
960
961         if (seq == NULL &&
962             key_value < 127 &&
963             isprint(key_value)) {
964                 key_char[1] = (char) key_value;
965                 seq = key_char;
966         }
967
968         return seq ? seq : "'?'";
969 }
970
971 static char *
972 get_key(enum request request)
973 {
974         static char buf[BUFSIZ];
975         size_t pos = 0;
976         char *sep = "";
977         int i;
978
979         buf[pos] = 0;
980
981         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
982                 struct keybinding *keybinding = &default_keybindings[i];
983
984                 if (keybinding->request != request)
985                         continue;
986
987                 if (!string_format_from(buf, &pos, "%s%s", sep,
988                                         get_key_name(keybinding->alias)))
989                         return "Too many keybindings!";
990                 sep = ", ";
991         }
992
993         return buf;
994 }
995
996 struct run_request {
997         enum keymap keymap;
998         int key;
999         char cmd[SIZEOF_STR];
1000 };
1001
1002 static struct run_request *run_request;
1003 static size_t run_requests;
1004
1005 static enum request
1006 add_run_request(enum keymap keymap, int key, int argc, char **argv)
1007 {
1008         struct run_request *tmp;
1009         struct run_request req = { keymap, key };
1010         size_t bufpos;
1011
1012         for (bufpos = 0; argc > 0; argc--, argv++)
1013                 if (!string_format_from(req.cmd, &bufpos, "%s ", *argv))
1014                         return REQ_NONE;
1015
1016         req.cmd[bufpos - 1] = 0;
1017
1018         tmp = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
1019         if (!tmp)
1020                 return REQ_NONE;
1021
1022         run_request = tmp;
1023         run_request[run_requests++] = req;
1024
1025         return REQ_NONE + run_requests;
1026 }
1027
1028 static struct run_request *
1029 get_run_request(enum request request)
1030 {
1031         if (request <= REQ_NONE)
1032                 return NULL;
1033         return &run_request[request - REQ_NONE - 1];
1034 }
1035
1036 static void
1037 add_builtin_run_requests(void)
1038 {
1039         struct {
1040                 enum keymap keymap;
1041                 int key;
1042                 char *argv[1];
1043         } reqs[] = {
1044                 { KEYMAP_MAIN,    'C', { "git cherry-pick %(commit)" } },
1045                 { KEYMAP_GENERIC, 'G', { "git gc" } },
1046         };
1047         int i;
1048
1049         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1050                 enum request req;
1051
1052                 req = add_run_request(reqs[i].keymap, reqs[i].key, 1, reqs[i].argv);
1053                 if (req != REQ_NONE)
1054                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1055         }
1056 }
1057
1058 /*
1059  * User config file handling.
1060  */
1061
1062 static struct int_map color_map[] = {
1063 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
1064         COLOR_MAP(DEFAULT),
1065         COLOR_MAP(BLACK),
1066         COLOR_MAP(BLUE),
1067         COLOR_MAP(CYAN),
1068         COLOR_MAP(GREEN),
1069         COLOR_MAP(MAGENTA),
1070         COLOR_MAP(RED),
1071         COLOR_MAP(WHITE),
1072         COLOR_MAP(YELLOW),
1073 };
1074
1075 #define set_color(color, name) \
1076         set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
1077
1078 static struct int_map attr_map[] = {
1079 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
1080         ATTR_MAP(NORMAL),
1081         ATTR_MAP(BLINK),
1082         ATTR_MAP(BOLD),
1083         ATTR_MAP(DIM),
1084         ATTR_MAP(REVERSE),
1085         ATTR_MAP(STANDOUT),
1086         ATTR_MAP(UNDERLINE),
1087 };
1088
1089 #define set_attribute(attr, name) \
1090         set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
1091
1092 static int   config_lineno;
1093 static bool  config_errors;
1094 static char *config_msg;
1095
1096 /* Wants: object fgcolor bgcolor [attr] */
1097 static int
1098 option_color_command(int argc, char *argv[])
1099 {
1100         struct line_info *info;
1101
1102         if (argc != 3 && argc != 4) {
1103                 config_msg = "Wrong number of arguments given to color command";
1104                 return ERR;
1105         }
1106
1107         info = get_line_info(argv[0], strlen(argv[0]));
1108         if (!info) {
1109                 config_msg = "Unknown color name";
1110                 return ERR;
1111         }
1112
1113         if (set_color(&info->fg, argv[1]) == ERR ||
1114             set_color(&info->bg, argv[2]) == ERR) {
1115                 config_msg = "Unknown color";
1116                 return ERR;
1117         }
1118
1119         if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1120                 config_msg = "Unknown attribute";
1121                 return ERR;
1122         }
1123
1124         return OK;
1125 }
1126
1127 /* Wants: name = value */
1128 static int
1129 option_set_command(int argc, char *argv[])
1130 {
1131         if (argc != 3) {
1132                 config_msg = "Wrong number of arguments given to set command";
1133                 return ERR;
1134         }
1135
1136         if (strcmp(argv[1], "=")) {
1137                 config_msg = "No value assigned";
1138                 return ERR;
1139         }
1140
1141         if (!strcmp(argv[0], "show-rev-graph")) {
1142                 opt_rev_graph = (!strcmp(argv[2], "1") ||
1143                                  !strcmp(argv[2], "true") ||
1144                                  !strcmp(argv[2], "yes"));
1145                 return OK;
1146         }
1147
1148         if (!strcmp(argv[0], "line-number-interval")) {
1149                 opt_num_interval = atoi(argv[2]);
1150                 return OK;
1151         }
1152
1153         if (!strcmp(argv[0], "tab-size")) {
1154                 opt_tab_size = atoi(argv[2]);
1155                 return OK;
1156         }
1157
1158         if (!strcmp(argv[0], "commit-encoding")) {
1159                 char *arg = argv[2];
1160                 int delimiter = *arg;
1161                 int i;
1162
1163                 switch (delimiter) {
1164                 case '"':
1165                 case '\'':
1166                         for (arg++, i = 0; arg[i]; i++)
1167                                 if (arg[i] == delimiter) {
1168                                         arg[i] = 0;
1169                                         break;
1170                                 }
1171                 default:
1172                         string_ncopy(opt_encoding, arg, strlen(arg));
1173                         return OK;
1174                 }
1175         }
1176
1177         config_msg = "Unknown variable name";
1178         return ERR;
1179 }
1180
1181 /* Wants: mode request key */
1182 static int
1183 option_bind_command(int argc, char *argv[])
1184 {
1185         enum request request;
1186         int keymap;
1187         int key;
1188
1189         if (argc < 3) {
1190                 config_msg = "Wrong number of arguments given to bind command";
1191                 return ERR;
1192         }
1193
1194         if (set_keymap(&keymap, argv[0]) == ERR) {
1195                 config_msg = "Unknown key map";
1196                 return ERR;
1197         }
1198
1199         key = get_key_value(argv[1]);
1200         if (key == ERR) {
1201                 config_msg = "Unknown key";
1202                 return ERR;
1203         }
1204
1205         request = get_request(argv[2]);
1206         if (request == REQ_NONE) {
1207                 const char *obsolete[] = { "cherry-pick" };
1208                 size_t namelen = strlen(argv[2]);
1209                 int i;
1210
1211                 for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
1212                         if (namelen == strlen(obsolete[i]) &&
1213                             !string_enum_compare(obsolete[i], argv[2], namelen)) {
1214                                 config_msg = "Obsolete request name";
1215                                 return ERR;
1216                         }
1217                 }
1218         }
1219         if (request == REQ_NONE && *argv[2]++ == '!')
1220                 request = add_run_request(keymap, key, argc - 2, argv + 2);
1221         if (request == REQ_NONE) {
1222                 config_msg = "Unknown request name";
1223                 return ERR;
1224         }
1225
1226         add_keybinding(keymap, request, key);
1227
1228         return OK;
1229 }
1230
1231 static int
1232 set_option(char *opt, char *value)
1233 {
1234         char *argv[16];
1235         int valuelen;
1236         int argc = 0;
1237
1238         /* Tokenize */
1239         while (argc < ARRAY_SIZE(argv) && (valuelen = strcspn(value, " \t"))) {
1240                 argv[argc++] = value;
1241                 value += valuelen;
1242
1243                 /* Nothing more to tokenize or last available token. */
1244                 if (!*value || argc >= ARRAY_SIZE(argv))
1245                         break;
1246
1247                 *value++ = 0;
1248                 while (isspace(*value))
1249                         value++;
1250         }
1251
1252         if (!strcmp(opt, "color"))
1253                 return option_color_command(argc, argv);
1254
1255         if (!strcmp(opt, "set"))
1256                 return option_set_command(argc, argv);
1257
1258         if (!strcmp(opt, "bind"))
1259                 return option_bind_command(argc, argv);
1260
1261         config_msg = "Unknown option command";
1262         return ERR;
1263 }
1264
1265 static int
1266 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1267 {
1268         int status = OK;
1269
1270         config_lineno++;
1271         config_msg = "Internal error";
1272
1273         /* Check for comment markers, since read_properties() will
1274          * only ensure opt and value are split at first " \t". */
1275         optlen = strcspn(opt, "#");
1276         if (optlen == 0)
1277                 return OK;
1278
1279         if (opt[optlen] != 0) {
1280                 config_msg = "No option value";
1281                 status = ERR;
1282
1283         }  else {
1284                 /* Look for comment endings in the value. */
1285                 size_t len = strcspn(value, "#");
1286
1287                 if (len < valuelen) {
1288                         valuelen = len;
1289                         value[valuelen] = 0;
1290                 }
1291
1292                 status = set_option(opt, value);
1293         }
1294
1295         if (status == ERR) {
1296                 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1297                         config_lineno, (int) optlen, opt, config_msg);
1298                 config_errors = TRUE;
1299         }
1300
1301         /* Always keep going if errors are encountered. */
1302         return OK;
1303 }
1304
1305 static void
1306 load_option_file(const char *path)
1307 {
1308         FILE *file;
1309
1310         /* It's ok that the file doesn't exist. */
1311         file = fopen(path, "r");
1312         if (!file)
1313                 return;
1314
1315         config_lineno = 0;
1316         config_errors = FALSE;
1317
1318         if (read_properties(file, " \t", read_option) == ERR ||
1319             config_errors == TRUE)
1320                 fprintf(stderr, "Errors while loading %s.\n", path);
1321 }
1322
1323 static int
1324 load_options(void)
1325 {
1326         char *home = getenv("HOME");
1327         char *tigrc_user = getenv("TIGRC_USER");
1328         char *tigrc_system = getenv("TIGRC_SYSTEM");
1329         char buf[SIZEOF_STR];
1330
1331         add_builtin_run_requests();
1332
1333         if (!tigrc_system) {
1334                 if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
1335                         return ERR;
1336                 tigrc_system = buf;
1337         }
1338         load_option_file(tigrc_system);
1339
1340         if (!tigrc_user) {
1341                 if (!home || !string_format(buf, "%s/.tigrc", home))
1342                         return ERR;
1343                 tigrc_user = buf;
1344         }
1345         load_option_file(tigrc_user);
1346
1347         return OK;
1348 }
1349
1350
1351 /*
1352  * The viewer
1353  */
1354
1355 struct view;
1356 struct view_ops;
1357
1358 /* The display array of active views and the index of the current view. */
1359 static struct view *display[2];
1360 static unsigned int current_view;
1361
1362 /* Reading from the prompt? */
1363 static bool input_mode = FALSE;
1364
1365 #define foreach_displayed_view(view, i) \
1366         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1367
1368 #define displayed_views()       (display[1] != NULL ? 2 : 1)
1369
1370 /* Current head and commit ID */
1371 static char ref_blob[SIZEOF_REF]        = "";
1372 static char ref_commit[SIZEOF_REF]      = "HEAD";
1373 static char ref_head[SIZEOF_REF]        = "HEAD";
1374
1375 struct view {
1376         const char *name;       /* View name */
1377         const char *cmd_fmt;    /* Default command line format */
1378         const char *cmd_env;    /* Command line set via environment */
1379         const char *id;         /* Points to either of ref_{head,commit,blob} */
1380
1381         struct view_ops *ops;   /* View operations */
1382
1383         enum keymap keymap;     /* What keymap does this view have */
1384
1385         char cmd[SIZEOF_STR];   /* Command buffer */
1386         char ref[SIZEOF_REF];   /* Hovered commit reference */
1387         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
1388
1389         int height, width;      /* The width and height of the main window */
1390         WINDOW *win;            /* The main window */
1391         WINDOW *title;          /* The title window living below the main window */
1392
1393         /* Navigation */
1394         unsigned long offset;   /* Offset of the window top */
1395         unsigned long lineno;   /* Current line number */
1396
1397         /* Searching */
1398         char grep[SIZEOF_STR];  /* Search string */
1399         regex_t *regex;         /* Pre-compiled regex */
1400
1401         /* If non-NULL, points to the view that opened this view. If this view
1402          * is closed tig will switch back to the parent view. */
1403         struct view *parent;
1404
1405         /* Buffering */
1406         unsigned long lines;    /* Total number of lines */
1407         struct line *line;      /* Line index */
1408         unsigned long line_size;/* Total number of allocated lines */
1409         unsigned int digits;    /* Number of digits in the lines member. */
1410
1411         /* Loading */
1412         FILE *pipe;
1413         time_t start_time;
1414 };
1415
1416 struct view_ops {
1417         /* What type of content being displayed. Used in the title bar. */
1418         const char *type;
1419         /* Open and reads in all view content. */
1420         bool (*open)(struct view *view);
1421         /* Read one line; updates view->line. */
1422         bool (*read)(struct view *view, char *data);
1423         /* Draw one line; @lineno must be < view->height. */
1424         bool (*draw)(struct view *view, struct line *line, unsigned int lineno, bool selected);
1425         /* Depending on view handle a special requests. */
1426         enum request (*request)(struct view *view, enum request request, struct line *line);
1427         /* Search for regex in a line. */
1428         bool (*grep)(struct view *view, struct line *line);
1429         /* Select line */
1430         void (*select)(struct view *view, struct line *line);
1431 };
1432
1433 static struct view_ops pager_ops;
1434 static struct view_ops main_ops;
1435 static struct view_ops tree_ops;
1436 static struct view_ops blob_ops;
1437 static struct view_ops help_ops;
1438 static struct view_ops status_ops;
1439 static struct view_ops stage_ops;
1440
1441 #define VIEW_STR(name, cmd, env, ref, ops, map) \
1442         { name, cmd, #env, ref, ops, map}
1443
1444 #define VIEW_(id, name, ops, ref) \
1445         VIEW_STR(name, TIG_##id##_CMD,  TIG_##id##_CMD, ref, ops, KEYMAP_##id)
1446
1447
1448 static struct view views[] = {
1449         VIEW_(MAIN,   "main",   &main_ops,   ref_head),
1450         VIEW_(DIFF,   "diff",   &pager_ops,  ref_commit),
1451         VIEW_(LOG,    "log",    &pager_ops,  ref_head),
1452         VIEW_(TREE,   "tree",   &tree_ops,   ref_commit),
1453         VIEW_(BLOB,   "blob",   &blob_ops,   ref_blob),
1454         VIEW_(HELP,   "help",   &help_ops,   ""),
1455         VIEW_(PAGER,  "pager",  &pager_ops,  "stdin"),
1456         VIEW_(STATUS, "status", &status_ops, ""),
1457         VIEW_(STAGE,  "stage",  &stage_ops,  ""),
1458 };
1459
1460 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1461
1462 #define foreach_view(view, i) \
1463         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1464
1465 #define view_is_displayed(view) \
1466         (view == display[0] || view == display[1])
1467
1468 static int
1469 draw_text(struct view *view, const char *string, int max_len, int col,
1470           bool use_tilde, int tilde_attr)
1471 {
1472         int len = 0;
1473         int trimmed = FALSE;
1474
1475         if (max_len <= 0)
1476                 return 0;
1477
1478         if (opt_utf8) {
1479                 len = utf8_length(string, max_len, &trimmed, use_tilde);
1480         } else {
1481                 len = strlen(string);
1482                 if (len > max_len) {
1483                         if (use_tilde) {
1484                                 max_len -= 1;
1485                         }
1486                         len = max_len;
1487                         trimmed = TRUE;
1488                 }
1489         }
1490
1491         waddnstr(view->win, string, len);
1492         if (trimmed && use_tilde) {
1493                 if (tilde_attr != -1)
1494                         wattrset(view->win, tilde_attr);
1495                 waddch(view->win, '~');
1496                 len++;
1497         }
1498
1499         return len;
1500 }
1501
1502 static bool
1503 draw_view_line(struct view *view, unsigned int lineno)
1504 {
1505         struct line *line;
1506         bool selected = (view->offset + lineno == view->lineno);
1507         bool draw_ok;
1508
1509         assert(view_is_displayed(view));
1510
1511         if (view->offset + lineno >= view->lines)
1512                 return FALSE;
1513
1514         line = &view->line[view->offset + lineno];
1515
1516         if (selected) {
1517                 line->selected = TRUE;
1518                 view->ops->select(view, line);
1519         } else if (line->selected) {
1520                 line->selected = FALSE;
1521                 wmove(view->win, lineno, 0);
1522                 wclrtoeol(view->win);
1523         }
1524
1525         scrollok(view->win, FALSE);
1526         draw_ok = view->ops->draw(view, line, lineno, selected);
1527         scrollok(view->win, TRUE);
1528
1529         return draw_ok;
1530 }
1531
1532 static void
1533 redraw_view_from(struct view *view, int lineno)
1534 {
1535         assert(0 <= lineno && lineno < view->height);
1536
1537         for (; lineno < view->height; lineno++) {
1538                 if (!draw_view_line(view, lineno))
1539                         break;
1540         }
1541
1542         redrawwin(view->win);
1543         if (input_mode)
1544                 wnoutrefresh(view->win);
1545         else
1546                 wrefresh(view->win);
1547 }
1548
1549 static void
1550 redraw_view(struct view *view)
1551 {
1552         wclear(view->win);
1553         redraw_view_from(view, 0);
1554 }
1555
1556
1557 static void
1558 update_view_title(struct view *view)
1559 {
1560         char buf[SIZEOF_STR];
1561         char state[SIZEOF_STR];
1562         size_t bufpos = 0, statelen = 0;
1563
1564         assert(view_is_displayed(view));
1565
1566         if (view != VIEW(REQ_VIEW_STATUS) && (view->lines || view->pipe)) {
1567                 unsigned int view_lines = view->offset + view->height;
1568                 unsigned int lines = view->lines
1569                                    ? MIN(view_lines, view->lines) * 100 / view->lines
1570                                    : 0;
1571
1572                 string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
1573                                    view->ops->type,
1574                                    view->lineno + 1,
1575                                    view->lines,
1576                                    lines);
1577
1578                 if (view->pipe) {
1579                         time_t secs = time(NULL) - view->start_time;
1580
1581                         /* Three git seconds are a long time ... */
1582                         if (secs > 2)
1583                                 string_format_from(state, &statelen, " %lds", secs);
1584                 }
1585         }
1586
1587         string_format_from(buf, &bufpos, "[%s]", view->name);
1588         if (*view->ref && bufpos < view->width) {
1589                 size_t refsize = strlen(view->ref);
1590                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
1591
1592                 if (minsize < view->width)
1593                         refsize = view->width - minsize + 7;
1594                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
1595         }
1596
1597         if (statelen && bufpos < view->width) {
1598                 string_format_from(buf, &bufpos, " %s", state);
1599         }
1600
1601         if (view == display[current_view])
1602                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
1603         else
1604                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
1605
1606         mvwaddnstr(view->title, 0, 0, buf, bufpos);
1607         wclrtoeol(view->title);
1608         wmove(view->title, 0, view->width - 1);
1609
1610         if (input_mode)
1611                 wnoutrefresh(view->title);
1612         else
1613                 wrefresh(view->title);
1614 }
1615
1616 static void
1617 resize_display(void)
1618 {
1619         int offset, i;
1620         struct view *base = display[0];
1621         struct view *view = display[1] ? display[1] : display[0];
1622
1623         /* Setup window dimensions */
1624
1625         getmaxyx(stdscr, base->height, base->width);
1626
1627         /* Make room for the status window. */
1628         base->height -= 1;
1629
1630         if (view != base) {
1631                 /* Horizontal split. */
1632                 view->width   = base->width;
1633                 view->height  = SCALE_SPLIT_VIEW(base->height);
1634                 base->height -= view->height;
1635
1636                 /* Make room for the title bar. */
1637                 view->height -= 1;
1638         }
1639
1640         /* Make room for the title bar. */
1641         base->height -= 1;
1642
1643         offset = 0;
1644
1645         foreach_displayed_view (view, i) {
1646                 if (!view->win) {
1647                         view->win = newwin(view->height, 0, offset, 0);
1648                         if (!view->win)
1649                                 die("Failed to create %s view", view->name);
1650
1651                         scrollok(view->win, TRUE);
1652
1653                         view->title = newwin(1, 0, offset + view->height, 0);
1654                         if (!view->title)
1655                                 die("Failed to create title window");
1656
1657                 } else {
1658                         wresize(view->win, view->height, view->width);
1659                         mvwin(view->win,   offset, 0);
1660                         mvwin(view->title, offset + view->height, 0);
1661                 }
1662
1663                 offset += view->height + 1;
1664         }
1665 }
1666
1667 static void
1668 redraw_display(void)
1669 {
1670         struct view *view;
1671         int i;
1672
1673         foreach_displayed_view (view, i) {
1674                 redraw_view(view);
1675                 update_view_title(view);
1676         }
1677 }
1678
1679 static void
1680 update_display_cursor(struct view *view)
1681 {
1682         /* Move the cursor to the right-most column of the cursor line.
1683          *
1684          * XXX: This could turn out to be a bit expensive, but it ensures that
1685          * the cursor does not jump around. */
1686         if (view->lines) {
1687                 wmove(view->win, view->lineno - view->offset, view->width - 1);
1688                 wrefresh(view->win);
1689         }
1690 }
1691
1692 /*
1693  * Navigation
1694  */
1695
1696 /* Scrolling backend */
1697 static void
1698 do_scroll_view(struct view *view, int lines)
1699 {
1700         bool redraw_current_line = FALSE;
1701
1702         /* The rendering expects the new offset. */
1703         view->offset += lines;
1704
1705         assert(0 <= view->offset && view->offset < view->lines);
1706         assert(lines);
1707
1708         /* Move current line into the view. */
1709         if (view->lineno < view->offset) {
1710                 view->lineno = view->offset;
1711                 redraw_current_line = TRUE;
1712         } else if (view->lineno >= view->offset + view->height) {
1713                 view->lineno = view->offset + view->height - 1;
1714                 redraw_current_line = TRUE;
1715         }
1716
1717         assert(view->offset <= view->lineno && view->lineno < view->lines);
1718
1719         /* Redraw the whole screen if scrolling is pointless. */
1720         if (view->height < ABS(lines)) {
1721                 redraw_view(view);
1722
1723         } else {
1724                 int line = lines > 0 ? view->height - lines : 0;
1725                 int end = line + ABS(lines);
1726
1727                 wscrl(view->win, lines);
1728
1729                 for (; line < end; line++) {
1730                         if (!draw_view_line(view, line))
1731                                 break;
1732                 }
1733
1734                 if (redraw_current_line)
1735                         draw_view_line(view, view->lineno - view->offset);
1736         }
1737
1738         redrawwin(view->win);
1739         wrefresh(view->win);
1740         report("");
1741 }
1742
1743 /* Scroll frontend */
1744 static void
1745 scroll_view(struct view *view, enum request request)
1746 {
1747         int lines = 1;
1748
1749         assert(view_is_displayed(view));
1750
1751         switch (request) {
1752         case REQ_SCROLL_PAGE_DOWN:
1753                 lines = view->height;
1754         case REQ_SCROLL_LINE_DOWN:
1755                 if (view->offset + lines > view->lines)
1756                         lines = view->lines - view->offset;
1757
1758                 if (lines == 0 || view->offset + view->height >= view->lines) {
1759                         report("Cannot scroll beyond the last line");
1760                         return;
1761                 }
1762                 break;
1763
1764         case REQ_SCROLL_PAGE_UP:
1765                 lines = view->height;
1766         case REQ_SCROLL_LINE_UP:
1767                 if (lines > view->offset)
1768                         lines = view->offset;
1769
1770                 if (lines == 0) {
1771                         report("Cannot scroll beyond the first line");
1772                         return;
1773                 }
1774
1775                 lines = -lines;
1776                 break;
1777
1778         default:
1779                 die("request %d not handled in switch", request);
1780         }
1781
1782         do_scroll_view(view, lines);
1783 }
1784
1785 /* Cursor moving */
1786 static void
1787 move_view(struct view *view, enum request request)
1788 {
1789         int scroll_steps = 0;
1790         int steps;
1791
1792         switch (request) {
1793         case REQ_MOVE_FIRST_LINE:
1794                 steps = -view->lineno;
1795                 break;
1796
1797         case REQ_MOVE_LAST_LINE:
1798                 steps = view->lines - view->lineno - 1;
1799                 break;
1800
1801         case REQ_MOVE_PAGE_UP:
1802                 steps = view->height > view->lineno
1803                       ? -view->lineno : -view->height;
1804                 break;
1805
1806         case REQ_MOVE_PAGE_DOWN:
1807                 steps = view->lineno + view->height >= view->lines
1808                       ? view->lines - view->lineno - 1 : view->height;
1809                 break;
1810
1811         case REQ_MOVE_UP:
1812                 steps = -1;
1813                 break;
1814
1815         case REQ_MOVE_DOWN:
1816                 steps = 1;
1817                 break;
1818
1819         default:
1820                 die("request %d not handled in switch", request);
1821         }
1822
1823         if (steps <= 0 && view->lineno == 0) {
1824                 report("Cannot move beyond the first line");
1825                 return;
1826
1827         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
1828                 report("Cannot move beyond the last line");
1829                 return;
1830         }
1831
1832         /* Move the current line */
1833         view->lineno += steps;
1834         assert(0 <= view->lineno && view->lineno < view->lines);
1835
1836         /* Check whether the view needs to be scrolled */
1837         if (view->lineno < view->offset ||
1838             view->lineno >= view->offset + view->height) {
1839                 scroll_steps = steps;
1840                 if (steps < 0 && -steps > view->offset) {
1841                         scroll_steps = -view->offset;
1842
1843                 } else if (steps > 0) {
1844                         if (view->lineno == view->lines - 1 &&
1845                             view->lines > view->height) {
1846                                 scroll_steps = view->lines - view->offset - 1;
1847                                 if (scroll_steps >= view->height)
1848                                         scroll_steps -= view->height - 1;
1849                         }
1850                 }
1851         }
1852
1853         if (!view_is_displayed(view)) {
1854                 view->offset += scroll_steps;
1855                 assert(0 <= view->offset && view->offset < view->lines);
1856                 view->ops->select(view, &view->line[view->lineno]);
1857                 return;
1858         }
1859
1860         /* Repaint the old "current" line if we be scrolling */
1861         if (ABS(steps) < view->height)
1862                 draw_view_line(view, view->lineno - steps - view->offset);
1863
1864         if (scroll_steps) {
1865                 do_scroll_view(view, scroll_steps);
1866                 return;
1867         }
1868
1869         /* Draw the current line */
1870         draw_view_line(view, view->lineno - view->offset);
1871
1872         redrawwin(view->win);
1873         wrefresh(view->win);
1874         report("");
1875 }
1876
1877
1878 /*
1879  * Searching
1880  */
1881
1882 static void search_view(struct view *view, enum request request);
1883
1884 static bool
1885 find_next_line(struct view *view, unsigned long lineno, struct line *line)
1886 {
1887         assert(view_is_displayed(view));
1888
1889         if (!view->ops->grep(view, line))
1890                 return FALSE;
1891
1892         if (lineno - view->offset >= view->height) {
1893                 view->offset = lineno;
1894                 view->lineno = lineno;
1895                 redraw_view(view);
1896
1897         } else {
1898                 unsigned long old_lineno = view->lineno - view->offset;
1899
1900                 view->lineno = lineno;
1901                 draw_view_line(view, old_lineno);
1902
1903                 draw_view_line(view, view->lineno - view->offset);
1904                 redrawwin(view->win);
1905                 wrefresh(view->win);
1906         }
1907
1908         report("Line %ld matches '%s'", lineno + 1, view->grep);
1909         return TRUE;
1910 }
1911
1912 static void
1913 find_next(struct view *view, enum request request)
1914 {
1915         unsigned long lineno = view->lineno;
1916         int direction;
1917
1918         if (!*view->grep) {
1919                 if (!*opt_search)
1920                         report("No previous search");
1921                 else
1922                         search_view(view, request);
1923                 return;
1924         }
1925
1926         switch (request) {
1927         case REQ_SEARCH:
1928         case REQ_FIND_NEXT:
1929                 direction = 1;
1930                 break;
1931
1932         case REQ_SEARCH_BACK:
1933         case REQ_FIND_PREV:
1934                 direction = -1;
1935                 break;
1936
1937         default:
1938                 return;
1939         }
1940
1941         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
1942                 lineno += direction;
1943
1944         /* Note, lineno is unsigned long so will wrap around in which case it
1945          * will become bigger than view->lines. */
1946         for (; lineno < view->lines; lineno += direction) {
1947                 struct line *line = &view->line[lineno];
1948
1949                 if (find_next_line(view, lineno, line))
1950                         return;
1951         }
1952
1953         report("No match found for '%s'", view->grep);
1954 }
1955
1956 static void
1957 search_view(struct view *view, enum request request)
1958 {
1959         int regex_err;
1960
1961         if (view->regex) {
1962                 regfree(view->regex);
1963                 *view->grep = 0;
1964         } else {
1965                 view->regex = calloc(1, sizeof(*view->regex));
1966                 if (!view->regex)
1967                         return;
1968         }
1969
1970         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
1971         if (regex_err != 0) {
1972                 char buf[SIZEOF_STR] = "unknown error";
1973
1974                 regerror(regex_err, view->regex, buf, sizeof(buf));
1975                 report("Search failed: %s", buf);
1976                 return;
1977         }
1978
1979         string_copy(view->grep, opt_search);
1980
1981         find_next(view, request);
1982 }
1983
1984 /*
1985  * Incremental updating
1986  */
1987
1988 static void
1989 end_update(struct view *view)
1990 {
1991         if (!view->pipe)
1992                 return;
1993         set_nonblocking_input(FALSE);
1994         if (view->pipe == stdin)
1995                 fclose(view->pipe);
1996         else
1997                 pclose(view->pipe);
1998         view->pipe = NULL;
1999 }
2000
2001 static bool
2002 begin_update(struct view *view)
2003 {
2004         if (view->pipe)
2005                 end_update(view);
2006
2007         if (opt_cmd[0]) {
2008                 string_copy(view->cmd, opt_cmd);
2009                 opt_cmd[0] = 0;
2010                 /* When running random commands, initially show the
2011                  * command in the title. However, it maybe later be
2012                  * overwritten if a commit line is selected. */
2013                 if (view == VIEW(REQ_VIEW_PAGER))
2014                         string_copy(view->ref, view->cmd);
2015                 else
2016                         view->ref[0] = 0;
2017
2018         } else if (view == VIEW(REQ_VIEW_TREE)) {
2019                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
2020                 char path[SIZEOF_STR];
2021
2022                 if (strcmp(view->vid, view->id))
2023                         opt_path[0] = path[0] = 0;
2024                 else if (sq_quote(path, 0, opt_path) >= sizeof(path))
2025                         return FALSE;
2026
2027                 if (!string_format(view->cmd, format, view->id, path))
2028                         return FALSE;
2029
2030         } else {
2031                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
2032                 const char *id = view->id;
2033
2034                 if (!string_format(view->cmd, format, id, id, id, id, id))
2035                         return FALSE;
2036
2037                 /* Put the current ref_* value to the view title ref
2038                  * member. This is needed by the blob view. Most other
2039                  * views sets it automatically after loading because the
2040                  * first line is a commit line. */
2041                 string_copy_rev(view->ref, view->id);
2042         }
2043
2044         /* Special case for the pager view. */
2045         if (opt_pipe) {
2046                 view->pipe = opt_pipe;
2047                 opt_pipe = NULL;
2048         } else {
2049                 view->pipe = popen(view->cmd, "r");
2050         }
2051
2052         if (!view->pipe)
2053                 return FALSE;
2054
2055         set_nonblocking_input(TRUE);
2056
2057         view->offset = 0;
2058         view->lines  = 0;
2059         view->lineno = 0;
2060         string_copy_rev(view->vid, view->id);
2061
2062         if (view->line) {
2063                 int i;
2064
2065                 for (i = 0; i < view->lines; i++)
2066                         if (view->line[i].data)
2067                                 free(view->line[i].data);
2068
2069                 free(view->line);
2070                 view->line = NULL;
2071         }
2072
2073         view->start_time = time(NULL);
2074
2075         return TRUE;
2076 }
2077
2078 static struct line *
2079 realloc_lines(struct view *view, size_t line_size)
2080 {
2081         struct line *tmp = realloc(view->line, sizeof(*view->line) * line_size);
2082
2083         if (!tmp)
2084                 return NULL;
2085
2086         view->line = tmp;
2087         view->line_size = line_size;
2088         return view->line;
2089 }
2090
2091 static bool
2092 update_view(struct view *view)
2093 {
2094         char in_buffer[BUFSIZ];
2095         char out_buffer[BUFSIZ * 2];
2096         char *line;
2097         /* The number of lines to read. If too low it will cause too much
2098          * redrawing (and possible flickering), if too high responsiveness
2099          * will suffer. */
2100         unsigned long lines = view->height;
2101         int redraw_from = -1;
2102
2103         if (!view->pipe)
2104                 return TRUE;
2105
2106         /* Only redraw if lines are visible. */
2107         if (view->offset + view->height >= view->lines)
2108                 redraw_from = view->lines - view->offset;
2109
2110         /* FIXME: This is probably not perfect for backgrounded views. */
2111         if (!realloc_lines(view, view->lines + lines))
2112                 goto alloc_error;
2113
2114         while ((line = fgets(in_buffer, sizeof(in_buffer), view->pipe))) {
2115                 size_t linelen = strlen(line);
2116
2117                 if (linelen)
2118                         line[linelen - 1] = 0;
2119
2120                 if (opt_iconv != ICONV_NONE) {
2121                         ICONV_CONST char *inbuf = line;
2122                         size_t inlen = linelen;
2123
2124                         char *outbuf = out_buffer;
2125                         size_t outlen = sizeof(out_buffer);
2126
2127                         size_t ret;
2128
2129                         ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2130                         if (ret != (size_t) -1) {
2131                                 line = out_buffer;
2132                                 linelen = strlen(out_buffer);
2133                         }
2134                 }
2135
2136                 if (!view->ops->read(view, line))
2137                         goto alloc_error;
2138
2139                 if (lines-- == 1)
2140                         break;
2141         }
2142
2143         {
2144                 int digits;
2145
2146                 lines = view->lines;
2147                 for (digits = 0; lines; digits++)
2148                         lines /= 10;
2149
2150                 /* Keep the displayed view in sync with line number scaling. */
2151                 if (digits != view->digits) {
2152                         view->digits = digits;
2153                         redraw_from = 0;
2154                 }
2155         }
2156
2157         if (!view_is_displayed(view))
2158                 goto check_pipe;
2159
2160         if (view == VIEW(REQ_VIEW_TREE)) {
2161                 /* Clear the view and redraw everything since the tree sorting
2162                  * might have rearranged things. */
2163                 redraw_view(view);
2164
2165         } else if (redraw_from >= 0) {
2166                 /* If this is an incremental update, redraw the previous line
2167                  * since for commits some members could have changed when
2168                  * loading the main view. */
2169                 if (redraw_from > 0)
2170                         redraw_from--;
2171
2172                 /* Since revision graph visualization requires knowledge
2173                  * about the parent commit, it causes a further one-off
2174                  * needed to be redrawn for incremental updates. */
2175                 if (redraw_from > 0 && opt_rev_graph)
2176                         redraw_from--;
2177
2178                 /* Incrementally draw avoids flickering. */
2179                 redraw_view_from(view, redraw_from);
2180         }
2181
2182         /* Update the title _after_ the redraw so that if the redraw picks up a
2183          * commit reference in view->ref it'll be available here. */
2184         update_view_title(view);
2185
2186 check_pipe:
2187         if (ferror(view->pipe)) {
2188                 report("Failed to read: %s", strerror(errno));
2189                 goto end;
2190
2191         } else if (feof(view->pipe)) {
2192                 report("");
2193                 goto end;
2194         }
2195
2196         return TRUE;
2197
2198 alloc_error:
2199         report("Allocation failure");
2200
2201 end:
2202         view->ops->read(view, NULL);
2203         end_update(view);
2204         return FALSE;
2205 }
2206
2207 static struct line *
2208 add_line_data(struct view *view, void *data, enum line_type type)
2209 {
2210         struct line *line = &view->line[view->lines++];
2211
2212         memset(line, 0, sizeof(*line));
2213         line->type = type;
2214         line->data = data;
2215
2216         return line;
2217 }
2218
2219 static struct line *
2220 add_line_text(struct view *view, char *data, enum line_type type)
2221 {
2222         if (data)
2223                 data = strdup(data);
2224
2225         return data ? add_line_data(view, data, type) : NULL;
2226 }
2227
2228
2229 /*
2230  * View opening
2231  */
2232
2233 enum open_flags {
2234         OPEN_DEFAULT = 0,       /* Use default view switching. */
2235         OPEN_SPLIT = 1,         /* Split current view. */
2236         OPEN_BACKGROUNDED = 2,  /* Backgrounded. */
2237         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
2238 };
2239
2240 static void
2241 open_view(struct view *prev, enum request request, enum open_flags flags)
2242 {
2243         bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2244         bool split = !!(flags & OPEN_SPLIT);
2245         bool reload = !!(flags & OPEN_RELOAD);
2246         struct view *view = VIEW(request);
2247         int nviews = displayed_views();
2248         struct view *base_view = display[0];
2249
2250         if (view == prev && nviews == 1 && !reload) {
2251                 report("Already in %s view", view->name);
2252                 return;
2253         }
2254
2255         if (view->ops->open) {
2256                 if (!view->ops->open(view)) {
2257                         report("Failed to load %s view", view->name);
2258                         return;
2259                 }
2260
2261         } else if ((reload || strcmp(view->vid, view->id)) &&
2262                    !begin_update(view)) {
2263                 report("Failed to load %s view", view->name);
2264                 return;
2265         }
2266
2267         if (split) {
2268                 display[1] = view;
2269                 if (!backgrounded)
2270                         current_view = 1;
2271         } else {
2272                 /* Maximize the current view. */
2273                 memset(display, 0, sizeof(display));
2274                 current_view = 0;
2275                 display[current_view] = view;
2276         }
2277
2278         /* Resize the view when switching between split- and full-screen,
2279          * or when switching between two different full-screen views. */
2280         if (nviews != displayed_views() ||
2281             (nviews == 1 && base_view != display[0]))
2282                 resize_display();
2283
2284         if (split && prev->lineno - prev->offset >= prev->height) {
2285                 /* Take the title line into account. */
2286                 int lines = prev->lineno - prev->offset - prev->height + 1;
2287
2288                 /* Scroll the view that was split if the current line is
2289                  * outside the new limited view. */
2290                 do_scroll_view(prev, lines);
2291         }
2292
2293         if (prev && view != prev) {
2294                 if (split && !backgrounded) {
2295                         /* "Blur" the previous view. */
2296                         update_view_title(prev);
2297                 }
2298
2299                 view->parent = prev;
2300         }
2301
2302         if (view->pipe && view->lines == 0) {
2303                 /* Clear the old view and let the incremental updating refill
2304                  * the screen. */
2305                 wclear(view->win);
2306                 report("");
2307         } else {
2308                 redraw_view(view);
2309                 report("");
2310         }
2311
2312         /* If the view is backgrounded the above calls to report()
2313          * won't redraw the view title. */
2314         if (backgrounded)
2315                 update_view_title(view);
2316 }
2317
2318 static void
2319 open_external_viewer(const char *cmd)
2320 {
2321         def_prog_mode();           /* save current tty modes */
2322         endwin();                  /* restore original tty modes */
2323         system(cmd);
2324         fprintf(stderr, "Press Enter to continue");
2325         getc(stdin);
2326         reset_prog_mode();
2327         redraw_display();
2328 }
2329
2330 static void
2331 open_mergetool(const char *file)
2332 {
2333         char cmd[SIZEOF_STR];
2334         char file_sq[SIZEOF_STR];
2335
2336         if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2337             string_format(cmd, "git mergetool %s", file_sq)) {
2338                 open_external_viewer(cmd);
2339         }
2340 }
2341
2342 static void
2343 open_editor(bool from_root, const char *file)
2344 {
2345         char cmd[SIZEOF_STR];
2346         char file_sq[SIZEOF_STR];
2347         char *editor;
2348         char *prefix = from_root ? opt_cdup : "";
2349
2350         editor = getenv("GIT_EDITOR");
2351         if (!editor && *opt_editor)
2352                 editor = opt_editor;
2353         if (!editor)
2354                 editor = getenv("VISUAL");
2355         if (!editor)
2356                 editor = getenv("EDITOR");
2357         if (!editor)
2358                 editor = "vi";
2359
2360         if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2361             string_format(cmd, "%s %s%s", editor, prefix, file_sq)) {
2362                 open_external_viewer(cmd);
2363         }
2364 }
2365
2366 static void
2367 open_run_request(enum request request)
2368 {
2369         struct run_request *req = get_run_request(request);
2370         char buf[SIZEOF_STR * 2];
2371         size_t bufpos;
2372         char *cmd;
2373
2374         if (!req) {
2375                 report("Unknown run request");
2376                 return;
2377         }
2378
2379         bufpos = 0;
2380         cmd = req->cmd;
2381
2382         while (cmd) {
2383                 char *next = strstr(cmd, "%(");
2384                 int len = next - cmd;
2385                 char *value;
2386
2387                 if (!next) {
2388                         len = strlen(cmd);
2389                         value = "";
2390
2391                 } else if (!strncmp(next, "%(head)", 7)) {
2392                         value = ref_head;
2393
2394                 } else if (!strncmp(next, "%(commit)", 9)) {
2395                         value = ref_commit;
2396
2397                 } else if (!strncmp(next, "%(blob)", 7)) {
2398                         value = ref_blob;
2399
2400                 } else {
2401                         report("Unknown replacement in run request: `%s`", req->cmd);
2402                         return;
2403                 }
2404
2405                 if (!string_format_from(buf, &bufpos, "%.*s%s", len, cmd, value))
2406                         return;
2407
2408                 if (next)
2409                         next = strchr(next, ')') + 1;
2410                 cmd = next;
2411         }
2412
2413         open_external_viewer(buf);
2414 }
2415
2416 /*
2417  * User request switch noodle
2418  */
2419
2420 static int
2421 view_driver(struct view *view, enum request request)
2422 {
2423         int i;
2424
2425         if (request == REQ_NONE) {
2426                 doupdate();
2427                 return TRUE;
2428         }
2429
2430         if (request > REQ_NONE) {
2431                 open_run_request(request);
2432                 return TRUE;
2433         }
2434
2435         if (view && view->lines) {
2436                 request = view->ops->request(view, request, &view->line[view->lineno]);
2437                 if (request == REQ_NONE)
2438                         return TRUE;
2439         }
2440
2441         switch (request) {
2442         case REQ_MOVE_UP:
2443         case REQ_MOVE_DOWN:
2444         case REQ_MOVE_PAGE_UP:
2445         case REQ_MOVE_PAGE_DOWN:
2446         case REQ_MOVE_FIRST_LINE:
2447         case REQ_MOVE_LAST_LINE:
2448                 move_view(view, request);
2449                 break;
2450
2451         case REQ_SCROLL_LINE_DOWN:
2452         case REQ_SCROLL_LINE_UP:
2453         case REQ_SCROLL_PAGE_DOWN:
2454         case REQ_SCROLL_PAGE_UP:
2455                 scroll_view(view, request);
2456                 break;
2457
2458         case REQ_VIEW_BLOB:
2459                 if (!ref_blob[0]) {
2460                         report("No file chosen, press %s to open tree view",
2461                                get_key(REQ_VIEW_TREE));
2462                         break;
2463                 }
2464                 open_view(view, request, OPEN_DEFAULT);
2465                 break;
2466
2467         case REQ_VIEW_PAGER:
2468                 if (!opt_pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
2469                         report("No pager content, press %s to run command from prompt",
2470                                get_key(REQ_PROMPT));
2471                         break;
2472                 }
2473                 open_view(view, request, OPEN_DEFAULT);
2474                 break;
2475
2476         case REQ_VIEW_STAGE:
2477                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
2478                         report("No stage content, press %s to open the status view and choose file",
2479                                get_key(REQ_VIEW_STATUS));
2480                         break;
2481                 }
2482                 open_view(view, request, OPEN_DEFAULT);
2483                 break;
2484
2485         case REQ_VIEW_STATUS:
2486                 if (opt_is_inside_work_tree == FALSE) {
2487                         report("The status view requires a working tree");
2488                         break;
2489                 }
2490                 open_view(view, request, OPEN_DEFAULT);
2491                 break;
2492
2493         case REQ_VIEW_MAIN:
2494         case REQ_VIEW_DIFF:
2495         case REQ_VIEW_LOG:
2496         case REQ_VIEW_TREE:
2497         case REQ_VIEW_HELP:
2498                 open_view(view, request, OPEN_DEFAULT);
2499                 break;
2500
2501         case REQ_NEXT:
2502         case REQ_PREVIOUS:
2503                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
2504
2505                 if ((view == VIEW(REQ_VIEW_DIFF) &&
2506                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
2507                    (view == VIEW(REQ_VIEW_STAGE) &&
2508                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
2509                    (view == VIEW(REQ_VIEW_BLOB) &&
2510                      view->parent == VIEW(REQ_VIEW_TREE))) {
2511                         int line;
2512
2513                         view = view->parent;
2514                         line = view->lineno;
2515                         move_view(view, request);
2516                         if (view_is_displayed(view))
2517                                 update_view_title(view);
2518                         if (line != view->lineno)
2519                                 view->ops->request(view, REQ_ENTER,
2520                                                    &view->line[view->lineno]);
2521
2522                 } else {
2523                         move_view(view, request);
2524                 }
2525                 break;
2526
2527         case REQ_VIEW_NEXT:
2528         {
2529                 int nviews = displayed_views();
2530                 int next_view = (current_view + 1) % nviews;
2531
2532                 if (next_view == current_view) {
2533                         report("Only one view is displayed");
2534                         break;
2535                 }
2536
2537                 current_view = next_view;
2538                 /* Blur out the title of the previous view. */
2539                 update_view_title(view);
2540                 report("");
2541                 break;
2542         }
2543         case REQ_REFRESH:
2544                 report("Refreshing is not yet supported for the %s view", view->name);
2545                 break;
2546
2547         case REQ_TOGGLE_LINENO:
2548                 opt_line_number = !opt_line_number;
2549                 redraw_display();
2550                 break;
2551
2552         case REQ_TOGGLE_DATE:
2553                 opt_date = !opt_date;
2554                 redraw_display();
2555                 break;
2556
2557         case REQ_TOGGLE_AUTHOR:
2558                 opt_author = !opt_author;
2559                 redraw_display();
2560                 break;
2561
2562         case REQ_TOGGLE_REV_GRAPH:
2563                 opt_rev_graph = !opt_rev_graph;
2564                 redraw_display();
2565                 break;
2566
2567         case REQ_TOGGLE_REFS:
2568                 opt_show_refs = !opt_show_refs;
2569                 redraw_display();
2570                 break;
2571
2572         case REQ_PROMPT:
2573                 /* Always reload^Wrerun commands from the prompt. */
2574                 open_view(view, opt_request, OPEN_RELOAD);
2575                 break;
2576
2577         case REQ_SEARCH:
2578         case REQ_SEARCH_BACK:
2579                 search_view(view, request);
2580                 break;
2581
2582         case REQ_FIND_NEXT:
2583         case REQ_FIND_PREV:
2584                 find_next(view, request);
2585                 break;
2586
2587         case REQ_STOP_LOADING:
2588                 for (i = 0; i < ARRAY_SIZE(views); i++) {
2589                         view = &views[i];
2590                         if (view->pipe)
2591                                 report("Stopped loading the %s view", view->name),
2592                         end_update(view);
2593                 }
2594                 break;
2595
2596         case REQ_SHOW_VERSION:
2597                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
2598                 return TRUE;
2599
2600         case REQ_SCREEN_RESIZE:
2601                 resize_display();
2602                 /* Fall-through */
2603         case REQ_SCREEN_REDRAW:
2604                 redraw_display();
2605                 break;
2606
2607         case REQ_EDIT:
2608                 report("Nothing to edit");
2609                 break;
2610
2611
2612         case REQ_ENTER:
2613                 report("Nothing to enter");
2614                 break;
2615
2616
2617         case REQ_VIEW_CLOSE:
2618                 /* XXX: Mark closed views by letting view->parent point to the
2619                  * view itself. Parents to closed view should never be
2620                  * followed. */
2621                 if (view->parent &&
2622                     view->parent->parent != view->parent) {
2623                         memset(display, 0, sizeof(display));
2624                         current_view = 0;
2625                         display[current_view] = view->parent;
2626                         view->parent = view;
2627                         resize_display();
2628                         redraw_display();
2629                         break;
2630                 }
2631                 /* Fall-through */
2632         case REQ_QUIT:
2633                 return FALSE;
2634
2635         default:
2636                 /* An unknown key will show most commonly used commands. */
2637                 report("Unknown key, press 'h' for help");
2638                 return TRUE;
2639         }
2640
2641         return TRUE;
2642 }
2643
2644
2645 /*
2646  * Pager backend
2647  */
2648
2649 static bool
2650 pager_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
2651 {
2652         char *text = line->data;
2653         enum line_type type = line->type;
2654         int attr;
2655
2656         wmove(view->win, lineno, 0);
2657
2658         if (selected) {
2659                 type = LINE_CURSOR;
2660                 wchgat(view->win, -1, 0, type, NULL);
2661         }
2662
2663         attr = get_line_attr(type);
2664         wattrset(view->win, attr);
2665
2666         if (opt_line_number || opt_tab_size < TABSIZE) {
2667                 static char spaces[] = "                    ";
2668                 int col_offset = 0, col = 0;
2669
2670                 if (opt_line_number) {
2671                         unsigned long real_lineno = view->offset + lineno + 1;
2672
2673                         if (real_lineno == 1 ||
2674                             (real_lineno % opt_num_interval) == 0) {
2675                                 wprintw(view->win, "%.*d", view->digits, real_lineno);
2676
2677                         } else {
2678                                 waddnstr(view->win, spaces,
2679                                          MIN(view->digits, STRING_SIZE(spaces)));
2680                         }
2681                         waddstr(view->win, ": ");
2682                         col_offset = view->digits + 2;
2683                 }
2684
2685                 while (text && col_offset + col < view->width) {
2686                         int cols_max = view->width - col_offset - col;
2687                         char *pos = text;
2688                         int cols;
2689
2690                         if (*text == '\t') {
2691                                 text++;
2692                                 assert(sizeof(spaces) > TABSIZE);
2693                                 pos = spaces;
2694                                 cols = opt_tab_size - (col % opt_tab_size);
2695
2696                         } else {
2697                                 text = strchr(text, '\t');
2698                                 cols = line ? text - pos : strlen(pos);
2699                         }
2700
2701                         waddnstr(view->win, pos, MIN(cols, cols_max));
2702                         col += cols;
2703                 }
2704
2705         } else {
2706                 int tilde_attr = get_line_attr(LINE_MAIN_DELIM);
2707
2708                 draw_text(view, text, view->width, 0, TRUE, tilde_attr);
2709         }
2710
2711         return TRUE;
2712 }
2713
2714 static bool
2715 add_describe_ref(char *buf, size_t *bufpos, char *commit_id, const char *sep)
2716 {
2717         char refbuf[SIZEOF_STR];
2718         char *ref = NULL;
2719         FILE *pipe;
2720
2721         if (!string_format(refbuf, "git describe %s 2>/dev/null", commit_id))
2722                 return TRUE;
2723
2724         pipe = popen(refbuf, "r");
2725         if (!pipe)
2726                 return TRUE;
2727
2728         if ((ref = fgets(refbuf, sizeof(refbuf), pipe)))
2729                 ref = chomp_string(ref);
2730         pclose(pipe);
2731
2732         if (!ref || !*ref)
2733                 return TRUE;
2734
2735         /* This is the only fatal call, since it can "corrupt" the buffer. */
2736         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
2737                 return FALSE;
2738
2739         return TRUE;
2740 }
2741
2742 static void
2743 add_pager_refs(struct view *view, struct line *line)
2744 {
2745         char buf[SIZEOF_STR];
2746         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
2747         struct ref **refs;
2748         size_t bufpos = 0, refpos = 0;
2749         const char *sep = "Refs: ";
2750         bool is_tag = FALSE;
2751
2752         assert(line->type == LINE_COMMIT);
2753
2754         refs = get_refs(commit_id);
2755         if (!refs) {
2756                 if (view == VIEW(REQ_VIEW_DIFF))
2757                         goto try_add_describe_ref;
2758                 return;
2759         }
2760
2761         do {
2762                 struct ref *ref = refs[refpos];
2763                 char *fmt = ref->tag    ? "%s[%s]" :
2764                             ref->remote ? "%s<%s>" : "%s%s";
2765
2766                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
2767                         return;
2768                 sep = ", ";
2769                 if (ref->tag)
2770                         is_tag = TRUE;
2771         } while (refs[refpos++]->next);
2772
2773         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
2774 try_add_describe_ref:
2775                 /* Add <tag>-g<commit_id> "fake" reference. */
2776                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
2777                         return;
2778         }
2779
2780         if (bufpos == 0)
2781                 return;
2782
2783         if (!realloc_lines(view, view->line_size + 1))
2784                 return;
2785
2786         add_line_text(view, buf, LINE_PP_REFS);
2787 }
2788
2789 static bool
2790 pager_read(struct view *view, char *data)
2791 {
2792         struct line *line;
2793
2794         if (!data)
2795                 return TRUE;
2796
2797         line = add_line_text(view, data, get_line_type(data));
2798         if (!line)
2799                 return FALSE;
2800
2801         if (line->type == LINE_COMMIT &&
2802             (view == VIEW(REQ_VIEW_DIFF) ||
2803              view == VIEW(REQ_VIEW_LOG)))
2804                 add_pager_refs(view, line);
2805
2806         return TRUE;
2807 }
2808
2809 static enum request
2810 pager_request(struct view *view, enum request request, struct line *line)
2811 {
2812         int split = 0;
2813
2814         if (request != REQ_ENTER)
2815                 return request;
2816
2817         if (line->type == LINE_COMMIT &&
2818            (view == VIEW(REQ_VIEW_LOG) ||
2819             view == VIEW(REQ_VIEW_PAGER))) {
2820                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
2821                 split = 1;
2822         }
2823
2824         /* Always scroll the view even if it was split. That way
2825          * you can use Enter to scroll through the log view and
2826          * split open each commit diff. */
2827         scroll_view(view, REQ_SCROLL_LINE_DOWN);
2828
2829         /* FIXME: A minor workaround. Scrolling the view will call report("")
2830          * but if we are scrolling a non-current view this won't properly
2831          * update the view title. */
2832         if (split)
2833                 update_view_title(view);
2834
2835         return REQ_NONE;
2836 }
2837
2838 static bool
2839 pager_grep(struct view *view, struct line *line)
2840 {
2841         regmatch_t pmatch;
2842         char *text = line->data;
2843
2844         if (!*text)
2845                 return FALSE;
2846
2847         if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
2848                 return FALSE;
2849
2850         return TRUE;
2851 }
2852
2853 static void
2854 pager_select(struct view *view, struct line *line)
2855 {
2856         if (line->type == LINE_COMMIT) {
2857                 char *text = (char *)line->data + STRING_SIZE("commit ");
2858
2859                 if (view != VIEW(REQ_VIEW_PAGER))
2860                         string_copy_rev(view->ref, text);
2861                 string_copy_rev(ref_commit, text);
2862         }
2863 }
2864
2865 static struct view_ops pager_ops = {
2866         "line",
2867         NULL,
2868         pager_read,
2869         pager_draw,
2870         pager_request,
2871         pager_grep,
2872         pager_select,
2873 };
2874
2875
2876 /*
2877  * Help backend
2878  */
2879
2880 static bool
2881 help_open(struct view *view)
2882 {
2883         char buf[BUFSIZ];
2884         int lines = ARRAY_SIZE(req_info) + 2;
2885         int i;
2886
2887         if (view->lines > 0)
2888                 return TRUE;
2889
2890         for (i = 0; i < ARRAY_SIZE(req_info); i++)
2891                 if (!req_info[i].request)
2892                         lines++;
2893
2894         lines += run_requests + 1;
2895
2896         view->line = calloc(lines, sizeof(*view->line));
2897         if (!view->line)
2898                 return FALSE;
2899
2900         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
2901
2902         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
2903                 char *key;
2904
2905                 if (req_info[i].request == REQ_NONE)
2906                         continue;
2907
2908                 if (!req_info[i].request) {
2909                         add_line_text(view, "", LINE_DEFAULT);
2910                         add_line_text(view, req_info[i].help, LINE_DEFAULT);
2911                         continue;
2912                 }
2913
2914                 key = get_key(req_info[i].request);
2915                 if (!*key)
2916                         key = "(no key defined)";
2917
2918                 if (!string_format(buf, "    %-25s %s", key, req_info[i].help))
2919                         continue;
2920
2921                 add_line_text(view, buf, LINE_DEFAULT);
2922         }
2923
2924         if (run_requests) {
2925                 add_line_text(view, "", LINE_DEFAULT);
2926                 add_line_text(view, "External commands:", LINE_DEFAULT);
2927         }
2928
2929         for (i = 0; i < run_requests; i++) {
2930                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
2931                 char *key;
2932
2933                 if (!req)
2934                         continue;
2935
2936                 key = get_key_name(req->key);
2937                 if (!*key)
2938                         key = "(no key defined)";
2939
2940                 if (!string_format(buf, "    %-10s %-14s `%s`",
2941                                    keymap_table[req->keymap].name,
2942                                    key, req->cmd))
2943                         continue;
2944
2945                 add_line_text(view, buf, LINE_DEFAULT);
2946         }
2947
2948         return TRUE;
2949 }
2950
2951 static struct view_ops help_ops = {
2952         "line",
2953         help_open,
2954         NULL,
2955         pager_draw,
2956         pager_request,
2957         pager_grep,
2958         pager_select,
2959 };
2960
2961
2962 /*
2963  * Tree backend
2964  */
2965
2966 struct tree_stack_entry {
2967         struct tree_stack_entry *prev;  /* Entry below this in the stack */
2968         unsigned long lineno;           /* Line number to restore */
2969         char *name;                     /* Position of name in opt_path */
2970 };
2971
2972 /* The top of the path stack. */
2973 static struct tree_stack_entry *tree_stack = NULL;
2974 unsigned long tree_lineno = 0;
2975
2976 static void
2977 pop_tree_stack_entry(void)
2978 {
2979         struct tree_stack_entry *entry = tree_stack;
2980
2981         tree_lineno = entry->lineno;
2982         entry->name[0] = 0;
2983         tree_stack = entry->prev;
2984         free(entry);
2985 }
2986
2987 static void
2988 push_tree_stack_entry(char *name, unsigned long lineno)
2989 {
2990         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
2991         size_t pathlen = strlen(opt_path);
2992
2993         if (!entry)
2994                 return;
2995
2996         entry->prev = tree_stack;
2997         entry->name = opt_path + pathlen;
2998         tree_stack = entry;
2999
3000         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3001                 pop_tree_stack_entry();
3002                 return;
3003         }
3004
3005         /* Move the current line to the first tree entry. */
3006         tree_lineno = 1;
3007         entry->lineno = lineno;
3008 }
3009
3010 /* Parse output from git-ls-tree(1):
3011  *
3012  * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3013  * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3014  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3015  * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3016  */
3017
3018 #define SIZEOF_TREE_ATTR \
3019         STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3020
3021 #define TREE_UP_FORMAT "040000 tree %s\t.."
3022
3023 static int
3024 tree_compare_entry(enum line_type type1, char *name1,
3025                    enum line_type type2, char *name2)
3026 {
3027         if (type1 != type2) {
3028                 if (type1 == LINE_TREE_DIR)
3029                         return -1;
3030                 return 1;
3031         }
3032
3033         return strcmp(name1, name2);
3034 }
3035
3036 static bool
3037 tree_read(struct view *view, char *text)
3038 {
3039         size_t textlen = text ? strlen(text) : 0;
3040         char buf[SIZEOF_STR];
3041         unsigned long pos;
3042         enum line_type type;
3043         bool first_read = view->lines == 0;
3044
3045         if (textlen <= SIZEOF_TREE_ATTR)
3046                 return FALSE;
3047
3048         type = text[STRING_SIZE("100644 ")] == 't'
3049              ? LINE_TREE_DIR : LINE_TREE_FILE;
3050
3051         if (first_read) {
3052                 /* Add path info line */
3053                 if (!string_format(buf, "Directory path /%s", opt_path) ||
3054                     !realloc_lines(view, view->line_size + 1) ||
3055                     !add_line_text(view, buf, LINE_DEFAULT))
3056                         return FALSE;
3057
3058                 /* Insert "link" to parent directory. */
3059                 if (*opt_path) {
3060                         if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
3061                             !realloc_lines(view, view->line_size + 1) ||
3062                             !add_line_text(view, buf, LINE_TREE_DIR))
3063                                 return FALSE;
3064                 }
3065         }
3066
3067         /* Strip the path part ... */
3068         if (*opt_path) {
3069                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3070                 size_t striplen = strlen(opt_path);
3071                 char *path = text + SIZEOF_TREE_ATTR;
3072
3073                 if (pathlen > striplen)
3074                         memmove(path, path + striplen,
3075                                 pathlen - striplen + 1);
3076         }
3077
3078         /* Skip "Directory ..." and ".." line. */
3079         for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
3080                 struct line *line = &view->line[pos];
3081                 char *path1 = ((char *) line->data) + SIZEOF_TREE_ATTR;
3082                 char *path2 = text + SIZEOF_TREE_ATTR;
3083                 int cmp = tree_compare_entry(line->type, path1, type, path2);
3084
3085                 if (cmp <= 0)
3086                         continue;
3087
3088                 text = strdup(text);
3089                 if (!text)
3090                         return FALSE;
3091
3092                 if (view->lines > pos)
3093                         memmove(&view->line[pos + 1], &view->line[pos],
3094                                 (view->lines - pos) * sizeof(*line));
3095
3096                 line = &view->line[pos];
3097                 line->data = text;
3098                 line->type = type;
3099                 view->lines++;
3100                 return TRUE;
3101         }
3102
3103         if (!add_line_text(view, text, type))
3104                 return FALSE;
3105
3106         if (tree_lineno > view->lineno) {
3107                 view->lineno = tree_lineno;
3108                 tree_lineno = 0;
3109         }
3110
3111         return TRUE;
3112 }
3113
3114 static enum request
3115 tree_request(struct view *view, enum request request, struct line *line)
3116 {
3117         enum open_flags flags;
3118
3119         if (request == REQ_TREE_PARENT) {
3120                 if (*opt_path) {
3121                         /* fake 'cd  ..' */
3122                         request = REQ_ENTER;
3123                         line = &view->line[1];
3124                 } else {
3125                         /* quit view if at top of tree */
3126                         return REQ_VIEW_CLOSE;
3127                 }
3128         }
3129         if (request != REQ_ENTER)
3130                 return request;
3131
3132         /* Cleanup the stack if the tree view is at a different tree. */
3133         while (!*opt_path && tree_stack)
3134                 pop_tree_stack_entry();
3135
3136         switch (line->type) {
3137         case LINE_TREE_DIR:
3138                 /* Depending on whether it is a subdir or parent (updir?) link
3139                  * mangle the path buffer. */
3140                 if (line == &view->line[1] && *opt_path) {
3141                         pop_tree_stack_entry();
3142
3143                 } else {
3144                         char *data = line->data;
3145                         char *basename = data + SIZEOF_TREE_ATTR;
3146
3147                         push_tree_stack_entry(basename, view->lineno);
3148                 }
3149
3150                 /* Trees and subtrees share the same ID, so they are not not
3151                  * unique like blobs. */
3152                 flags = OPEN_RELOAD;
3153                 request = REQ_VIEW_TREE;
3154                 break;
3155
3156         case LINE_TREE_FILE:
3157                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3158                 request = REQ_VIEW_BLOB;
3159                 break;
3160
3161         default:
3162                 return TRUE;
3163         }
3164
3165         open_view(view, request, flags);
3166         if (request == REQ_VIEW_TREE) {
3167                 view->lineno = tree_lineno;
3168         }
3169
3170         return REQ_NONE;
3171 }
3172
3173 static void
3174 tree_select(struct view *view, struct line *line)
3175 {
3176         char *text = (char *)line->data + STRING_SIZE("100644 blob ");
3177
3178         if (line->type == LINE_TREE_FILE) {
3179                 string_copy_rev(ref_blob, text);
3180
3181         } else if (line->type != LINE_TREE_DIR) {
3182                 return;
3183         }
3184
3185         string_copy_rev(view->ref, text);
3186 }
3187
3188 static struct view_ops tree_ops = {
3189         "file",
3190         NULL,
3191         tree_read,
3192         pager_draw,
3193         tree_request,
3194         pager_grep,
3195         tree_select,
3196 };
3197
3198 static bool
3199 blob_read(struct view *view, char *line)
3200 {
3201         return add_line_text(view, line, LINE_DEFAULT) != NULL;
3202 }
3203
3204 static struct view_ops blob_ops = {
3205         "line",
3206         NULL,
3207         blob_read,
3208         pager_draw,
3209         pager_request,
3210         pager_grep,
3211         pager_select,
3212 };
3213
3214
3215 /*
3216  * Status backend
3217  */
3218
3219 struct status {
3220         char status;
3221         struct {
3222                 mode_t mode;
3223                 char rev[SIZEOF_REV];
3224                 char name[SIZEOF_STR];
3225         } old;
3226         struct {
3227                 mode_t mode;
3228                 char rev[SIZEOF_REV];
3229                 char name[SIZEOF_STR];
3230         } new;
3231 };
3232
3233 static struct status stage_status;
3234 static enum line_type stage_line_type;
3235
3236 /* Get fields from the diff line:
3237  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
3238  */
3239 static inline bool
3240 status_get_diff(struct status *file, char *buf, size_t bufsize)
3241 {
3242         char *old_mode = buf +  1;
3243         char *new_mode = buf +  8;
3244         char *old_rev  = buf + 15;
3245         char *new_rev  = buf + 56;
3246         char *status   = buf + 97;
3247
3248         if (bufsize < 99 ||
3249             old_mode[-1] != ':' ||
3250             new_mode[-1] != ' ' ||
3251             old_rev[-1]  != ' ' ||
3252             new_rev[-1]  != ' ' ||
3253             status[-1]   != ' ')
3254                 return FALSE;
3255
3256         file->status = *status;
3257
3258         string_copy_rev(file->old.rev, old_rev);
3259         string_copy_rev(file->new.rev, new_rev);
3260
3261         file->old.mode = strtoul(old_mode, NULL, 8);
3262         file->new.mode = strtoul(new_mode, NULL, 8);
3263
3264         file->old.name[0] = file->new.name[0] = 0;
3265
3266         return TRUE;
3267 }
3268
3269 static bool
3270 status_run(struct view *view, const char cmd[], bool diff, enum line_type type)
3271 {
3272         struct status *file = NULL;
3273         struct status *unmerged = NULL;
3274         char buf[SIZEOF_STR * 4];
3275         size_t bufsize = 0;
3276         FILE *pipe;
3277
3278         pipe = popen(cmd, "r");
3279         if (!pipe)
3280                 return FALSE;
3281
3282         add_line_data(view, NULL, type);
3283
3284         while (!feof(pipe) && !ferror(pipe)) {
3285                 char *sep;
3286                 size_t readsize;
3287
3288                 readsize = fread(buf + bufsize, 1, sizeof(buf) - bufsize, pipe);
3289                 if (!readsize)
3290                         break;
3291                 bufsize += readsize;
3292
3293                 /* Process while we have NUL chars. */
3294                 while ((sep = memchr(buf, 0, bufsize))) {
3295                         size_t sepsize = sep - buf + 1;
3296
3297                         if (!file) {
3298                                 if (!realloc_lines(view, view->line_size + 1))
3299                                         goto error_out;
3300
3301                                 file = calloc(1, sizeof(*file));
3302                                 if (!file)
3303                                         goto error_out;
3304
3305                                 add_line_data(view, file, type);
3306                         }
3307
3308                         /* Parse diff info part. */
3309                         if (!diff) {
3310                                 file->status = '?';
3311
3312                         } else if (!file->status) {
3313                                 if (!status_get_diff(file, buf, sepsize))
3314                                         goto error_out;
3315
3316                                 bufsize -= sepsize;
3317                                 memmove(buf, sep + 1, bufsize);
3318
3319                                 sep = memchr(buf, 0, bufsize);
3320                                 if (!sep)
3321                                         break;
3322                                 sepsize = sep - buf + 1;
3323
3324                                 /* Collapse all 'M'odified entries that
3325                                  * follow a associated 'U'nmerged entry.
3326                                  */
3327                                 if (file->status == 'U') {
3328                                         unmerged = file;
3329
3330                                 } else if (unmerged) {
3331                                         int collapse = !strcmp(buf, unmerged->new.name);
3332
3333                                         unmerged = NULL;
3334                                         if (collapse) {
3335                                                 free(file);
3336                                                 view->lines--;
3337                                                 continue;
3338                                         }
3339                                 }
3340                         }
3341
3342                         /* Grab the old name for rename/copy. */
3343                         if (!*file->old.name &&
3344                             (file->status == 'R' || file->status == 'C')) {
3345                                 sepsize = sep - buf + 1;
3346                                 string_ncopy(file->old.name, buf, sepsize);
3347                                 bufsize -= sepsize;
3348                                 memmove(buf, sep + 1, bufsize);
3349
3350                                 sep = memchr(buf, 0, bufsize);
3351                                 if (!sep)
3352                                         break;
3353                                 sepsize = sep - buf + 1;
3354                         }
3355
3356                         /* git-ls-files just delivers a NUL separated
3357                          * list of file names similar to the second half
3358                          * of the git-diff-* output. */
3359                         string_ncopy(file->new.name, buf, sepsize);
3360                         if (!*file->old.name)
3361                                 string_copy(file->old.name, file->new.name);
3362                         bufsize -= sepsize;
3363                         memmove(buf, sep + 1, bufsize);
3364                         file = NULL;
3365                 }
3366         }
3367
3368         if (ferror(pipe)) {
3369 error_out:
3370                 pclose(pipe);
3371                 return FALSE;
3372         }
3373
3374         if (!view->line[view->lines - 1].data)
3375                 add_line_data(view, NULL, LINE_STAT_NONE);
3376
3377         pclose(pipe);
3378         return TRUE;
3379 }
3380
3381 /* Don't show unmerged entries in the staged section. */
3382 #define STATUS_DIFF_INDEX_CMD "git diff-index -z --diff-filter=ACDMRTXB --cached -M HEAD"
3383 #define STATUS_DIFF_FILES_CMD "git diff-files -z"
3384 #define STATUS_LIST_OTHER_CMD \
3385         "git ls-files -z --others --exclude-per-directory=.gitignore"
3386
3387 #define STATUS_DIFF_INDEX_SHOW_CMD \
3388         "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s %s 2>/dev/null"
3389
3390 #define STATUS_DIFF_FILES_SHOW_CMD \
3391         "git diff-files --root --patch-with-stat -C -M -- %s %s 2>/dev/null"
3392
3393 /* First parse staged info using git-diff-index(1), then parse unstaged
3394  * info using git-diff-files(1), and finally untracked files using
3395  * git-ls-files(1). */
3396 static bool
3397 status_open(struct view *view)
3398 {
3399         struct stat statbuf;
3400         char exclude[SIZEOF_STR];
3401         char cmd[SIZEOF_STR];
3402         unsigned long prev_lineno = view->lineno;
3403         size_t i;
3404
3405         for (i = 0; i < view->lines; i++)
3406                 free(view->line[i].data);
3407         free(view->line);
3408         view->lines = view->line_size = view->lineno = 0;
3409         view->line = NULL;
3410
3411         if (!realloc_lines(view, view->line_size + 6))
3412                 return FALSE;
3413
3414         if (!string_format(exclude, "%s/info/exclude", opt_git_dir))
3415                 return FALSE;
3416
3417         string_copy(cmd, STATUS_LIST_OTHER_CMD);
3418
3419         if (stat(exclude, &statbuf) >= 0) {
3420                 size_t cmdsize = strlen(cmd);
3421
3422                 if (!string_format_from(cmd, &cmdsize, " %s", "--exclude-from=") ||
3423                     sq_quote(cmd, cmdsize, exclude) >= sizeof(cmd))
3424                         return FALSE;
3425         }
3426
3427         system("git update-index -q --refresh");
3428
3429         if (!status_run(view, STATUS_DIFF_INDEX_CMD, TRUE, LINE_STAT_STAGED) ||
3430             !status_run(view, STATUS_DIFF_FILES_CMD, TRUE, LINE_STAT_UNSTAGED) ||
3431             !status_run(view, cmd, FALSE, LINE_STAT_UNTRACKED))
3432                 return FALSE;
3433
3434         /* If all went well restore the previous line number to stay in
3435          * the context. */
3436         if (prev_lineno < view->lines)
3437                 view->lineno = prev_lineno;
3438         else
3439                 view->lineno = view->lines - 1;
3440
3441         return TRUE;
3442 }
3443
3444 static bool
3445 status_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
3446 {
3447         struct status *status = line->data;
3448         int tilde_attr = get_line_attr(LINE_MAIN_DELIM);
3449
3450         wmove(view->win, lineno, 0);
3451
3452         if (selected) {
3453                 wattrset(view->win, get_line_attr(LINE_CURSOR));
3454                 wchgat(view->win, -1, 0, LINE_CURSOR, NULL);
3455                 tilde_attr = -1;
3456
3457         } else if (!status && line->type != LINE_STAT_NONE) {
3458                 wattrset(view->win, get_line_attr(LINE_STAT_SECTION));
3459                 wchgat(view->win, -1, 0, LINE_STAT_SECTION, NULL);
3460
3461         } else {
3462                 wattrset(view->win, get_line_attr(line->type));
3463         }
3464
3465         if (!status) {
3466                 char *text;
3467
3468                 switch (line->type) {
3469                 case LINE_STAT_STAGED:
3470                         text = "Changes to be committed:";
3471                         break;
3472
3473                 case LINE_STAT_UNSTAGED:
3474                         text = "Changed but not updated:";
3475                         break;
3476
3477                 case LINE_STAT_UNTRACKED:
3478                         text = "Untracked files:";
3479                         break;
3480
3481                 case LINE_STAT_NONE:
3482                         text = "    (no files)";
3483                         break;
3484
3485                 default:
3486                         return FALSE;
3487                 }
3488
3489                 draw_text(view, text, view->width, 0, TRUE, tilde_attr);
3490                 return TRUE;
3491         }
3492
3493         waddch(view->win, status->status);
3494         if (!selected)
3495                 wattrset(view->win, A_NORMAL);
3496         wmove(view->win, lineno, 4);
3497         if (view->width < 5)
3498                 return TRUE;
3499
3500         draw_text(view, status->new.name, view->width - 5, 5, TRUE, tilde_attr);
3501         return TRUE;
3502 }
3503
3504 static enum request
3505 status_enter(struct view *view, struct line *line)
3506 {
3507         struct status *status = line->data;
3508         char oldpath[SIZEOF_STR] = "";
3509         char newpath[SIZEOF_STR] = "";
3510         char *info;
3511         size_t cmdsize = 0;
3512
3513         if (line->type == LINE_STAT_NONE ||
3514             (!status && line[1].type == LINE_STAT_NONE)) {
3515                 report("No file to diff");
3516                 return REQ_NONE;
3517         }
3518
3519         if (status) {
3520                 if (sq_quote(oldpath, 0, status->old.name) >= sizeof(oldpath))
3521                         return REQ_QUIT;
3522                 /* Diffs for unmerged entries are empty when pasing the
3523                  * new path, so leave it empty. */
3524                 if (status->status != 'U' &&
3525                     sq_quote(newpath, 0, status->new.name) >= sizeof(newpath))
3526                         return REQ_QUIT;
3527         }
3528
3529         if (opt_cdup[0] &&
3530             line->type != LINE_STAT_UNTRACKED &&
3531             !string_format_from(opt_cmd, &cmdsize, "cd %s;", opt_cdup))
3532                 return REQ_QUIT;
3533
3534         switch (line->type) {
3535         case LINE_STAT_STAGED:
3536                 if (!string_format_from(opt_cmd, &cmdsize,
3537                                         STATUS_DIFF_INDEX_SHOW_CMD, oldpath, newpath))
3538                         return REQ_QUIT;
3539                 if (status)
3540                         info = "Staged changes to %s";
3541                 else
3542                         info = "Staged changes";
3543                 break;
3544
3545         case LINE_STAT_UNSTAGED:
3546                 if (!string_format_from(opt_cmd, &cmdsize,
3547                                         STATUS_DIFF_FILES_SHOW_CMD, oldpath, newpath))
3548                         return REQ_QUIT;
3549                 if (status)
3550                         info = "Unstaged changes to %s";
3551                 else
3552                         info = "Unstaged changes";
3553                 break;
3554
3555         case LINE_STAT_UNTRACKED:
3556                 if (opt_pipe)
3557                         return REQ_QUIT;
3558
3559
3560                 if (!status) {
3561                         report("No file to show");
3562                         return REQ_NONE;
3563                 }
3564
3565                 opt_pipe = fopen(status->new.name, "r");
3566                 info = "Untracked file %s";
3567                 break;
3568
3569         default:
3570                 die("line type %d not handled in switch", line->type);
3571         }
3572
3573         open_view(view, REQ_VIEW_STAGE, OPEN_RELOAD | OPEN_SPLIT);
3574         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
3575                 if (status) {
3576                         stage_status = *status;
3577                 } else {
3578                         memset(&stage_status, 0, sizeof(stage_status));
3579                 }
3580
3581                 stage_line_type = line->type;
3582                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
3583         }
3584
3585         return REQ_NONE;
3586 }
3587
3588
3589 static bool
3590 status_update_file(struct view *view, struct status *status, enum line_type type)
3591 {
3592         char cmd[SIZEOF_STR];
3593         char buf[SIZEOF_STR];
3594         size_t cmdsize = 0;
3595         size_t bufsize = 0;
3596         size_t written = 0;
3597         FILE *pipe;
3598
3599         if (opt_cdup[0] &&
3600             type != LINE_STAT_UNTRACKED &&
3601             !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
3602                 return FALSE;
3603
3604         switch (type) {
3605         case LINE_STAT_STAGED:
3606                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
3607                                         status->old.mode,
3608                                         status->old.rev,
3609                                         status->old.name, 0))
3610                         return FALSE;
3611
3612                 string_add(cmd, cmdsize, "git update-index -z --index-info");
3613                 break;
3614
3615         case LINE_STAT_UNSTAGED:
3616         case LINE_STAT_UNTRACKED:
3617                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
3618                         return FALSE;
3619
3620                 string_add(cmd, cmdsize, "git update-index -z --add --remove --stdin");
3621                 break;
3622
3623         default:
3624                 die("line type %d not handled in switch", type);
3625         }
3626
3627         pipe = popen(cmd, "w");
3628         if (!pipe)
3629                 return FALSE;
3630
3631         while (!ferror(pipe) && written < bufsize) {
3632                 written += fwrite(buf + written, 1, bufsize - written, pipe);
3633         }
3634
3635         pclose(pipe);
3636
3637         if (written != bufsize)
3638                 return FALSE;
3639
3640         return TRUE;
3641 }
3642
3643 static void
3644 status_update(struct view *view)
3645 {
3646         struct line *line = &view->line[view->lineno];
3647
3648         assert(view->lines);
3649
3650         if (!line->data) {
3651                 while (++line < view->line + view->lines && line->data) {
3652                         if (!status_update_file(view, line->data, line->type))
3653                                 report("Failed to update file status");
3654                 }
3655
3656                 if (!line[-1].data) {
3657                         report("Nothing to update");
3658                         return;
3659                 }
3660
3661         } else if (!status_update_file(view, line->data, line->type)) {
3662                 report("Failed to update file status");
3663         }
3664 }
3665
3666 static enum request
3667 status_request(struct view *view, enum request request, struct line *line)
3668 {
3669         struct status *status = line->data;
3670
3671         switch (request) {
3672         case REQ_STATUS_UPDATE:
3673                 status_update(view);
3674                 break;
3675
3676         case REQ_STATUS_MERGE:
3677                 if (!status || status->status != 'U') {
3678                         report("Merging only possible for files with unmerged status ('U').");
3679                         return REQ_NONE;
3680                 }
3681                 open_mergetool(status->new.name);
3682                 break;
3683
3684         case REQ_EDIT:
3685                 if (!status)
3686                         return request;
3687
3688                 open_editor(status->status != '?', status->new.name);
3689                 break;
3690
3691         case REQ_ENTER:
3692                 /* After returning the status view has been split to
3693                  * show the stage view. No further reloading is
3694                  * necessary. */
3695                 status_enter(view, line);
3696                 return REQ_NONE;
3697
3698         case REQ_REFRESH:
3699                 /* Simply reload the view. */
3700                 break;
3701
3702         default:
3703                 return request;
3704         }
3705
3706         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
3707
3708         return REQ_NONE;
3709 }
3710
3711 static void
3712 status_select(struct view *view, struct line *line)
3713 {
3714         struct status *status = line->data;
3715         char file[SIZEOF_STR] = "all files";
3716         char *text;
3717         char *key;
3718
3719         if (status && !string_format(file, "'%s'", status->new.name))
3720                 return;
3721
3722         if (!status && line[1].type == LINE_STAT_NONE)
3723                 line++;
3724
3725         switch (line->type) {
3726         case LINE_STAT_STAGED:
3727                 text = "Press %s to unstage %s for commit";
3728                 break;
3729
3730         case LINE_STAT_UNSTAGED:
3731                 text = "Press %s to stage %s for commit";
3732                 break;
3733
3734         case LINE_STAT_UNTRACKED:
3735                 text = "Press %s to stage %s for addition";
3736                 break;
3737
3738         case LINE_STAT_NONE:
3739                 text = "Nothing to update";
3740                 break;
3741
3742         default:
3743                 die("line type %d not handled in switch", line->type);
3744         }
3745
3746         if (status && status->status == 'U') {
3747                 text = "Press %s to resolve conflict in %s";
3748                 key = get_key(REQ_STATUS_MERGE);
3749
3750         } else {
3751                 key = get_key(REQ_STATUS_UPDATE);
3752         }
3753
3754         string_format(view->ref, text, key, file);
3755 }
3756
3757 static bool
3758 status_grep(struct view *view, struct line *line)
3759 {
3760         struct status *status = line->data;
3761         enum { S_STATUS, S_NAME, S_END } state;
3762         char buf[2] = "?";
3763         regmatch_t pmatch;
3764
3765         if (!status)
3766                 return FALSE;
3767
3768         for (state = S_STATUS; state < S_END; state++) {
3769                 char *text;
3770
3771                 switch (state) {
3772                 case S_NAME:    text = status->new.name;        break;
3773                 case S_STATUS:
3774                         buf[0] = status->status;
3775                         text = buf;
3776                         break;
3777
3778                 default:
3779                         return FALSE;
3780                 }
3781
3782                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
3783                         return TRUE;
3784         }
3785
3786         return FALSE;
3787 }
3788
3789 static struct view_ops status_ops = {
3790         "file",
3791         status_open,
3792         NULL,
3793         status_draw,
3794         status_request,
3795         status_grep,
3796         status_select,
3797 };
3798
3799
3800 static bool
3801 stage_diff_line(FILE *pipe, struct line *line)
3802 {
3803         char *buf = line->data;
3804         size_t bufsize = strlen(buf);
3805         size_t written = 0;
3806
3807         while (!ferror(pipe) && written < bufsize) {
3808                 written += fwrite(buf + written, 1, bufsize - written, pipe);
3809         }
3810
3811         fputc('\n', pipe);
3812
3813         return written == bufsize;
3814 }
3815
3816 static struct line *
3817 stage_diff_hdr(struct view *view, struct line *line)
3818 {
3819         int diff_hdr_dir = line->type == LINE_DIFF_CHUNK ? -1 : 1;
3820         struct line *diff_hdr;
3821
3822         if (line->type == LINE_DIFF_CHUNK)
3823                 diff_hdr = line - 1;
3824         else
3825                 diff_hdr = view->line + 1;
3826
3827         while (diff_hdr > view->line && diff_hdr < view->line + view->lines) {
3828                 if (diff_hdr->type == LINE_DIFF_HEADER)
3829                         return diff_hdr;
3830
3831                 diff_hdr += diff_hdr_dir;
3832         }
3833
3834         return NULL;
3835 }
3836
3837 static bool
3838 stage_update_chunk(struct view *view, struct line *line)
3839 {
3840         char cmd[SIZEOF_STR];
3841         size_t cmdsize = 0;
3842         struct line *diff_hdr, *diff_chunk, *diff_end;
3843         FILE *pipe;
3844
3845         diff_hdr = stage_diff_hdr(view, line);
3846         if (!diff_hdr)
3847                 return FALSE;
3848
3849         if (opt_cdup[0] &&
3850             !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
3851                 return FALSE;
3852
3853         if (!string_format_from(cmd, &cmdsize,
3854                                 "git apply --cached %s - && "
3855                                 "git update-index -q --unmerged --refresh 2>/dev/null",
3856                                 stage_line_type == LINE_STAT_STAGED ? "-R" : ""))
3857                 return FALSE;
3858
3859         pipe = popen(cmd, "w");
3860         if (!pipe)
3861                 return FALSE;
3862
3863         diff_end = view->line + view->lines;
3864         if (line->type != LINE_DIFF_CHUNK) {
3865                 diff_chunk = diff_hdr;
3866
3867         } else {
3868                 for (diff_chunk = line + 1; diff_chunk < diff_end; diff_chunk++)
3869                         if (diff_chunk->type == LINE_DIFF_CHUNK ||
3870                             diff_chunk->type == LINE_DIFF_HEADER)
3871                                 diff_end = diff_chunk;
3872
3873                 diff_chunk = line;
3874
3875                 while (diff_hdr->type != LINE_DIFF_CHUNK) {
3876                         switch (diff_hdr->type) {
3877                         case LINE_DIFF_HEADER:
3878                         case LINE_DIFF_INDEX:
3879                         case LINE_DIFF_ADD:
3880                         case LINE_DIFF_DEL:
3881                                 break;
3882
3883                         default:
3884                                 diff_hdr++;
3885                                 continue;
3886                         }
3887
3888                         if (!stage_diff_line(pipe, diff_hdr++)) {
3889                                 pclose(pipe);
3890                                 return FALSE;
3891                         }
3892                 }
3893         }
3894
3895         while (diff_chunk < diff_end && stage_diff_line(pipe, diff_chunk))
3896                 diff_chunk++;
3897
3898         pclose(pipe);
3899
3900         if (diff_chunk != diff_end)
3901                 return FALSE;
3902
3903         return TRUE;
3904 }
3905
3906 static void
3907 stage_update(struct view *view, struct line *line)
3908 {
3909         if (stage_line_type != LINE_STAT_UNTRACKED &&
3910             (line->type == LINE_DIFF_CHUNK || !stage_status.status)) {
3911                 if (!stage_update_chunk(view, line)) {
3912                         report("Failed to apply chunk");
3913                         return;
3914                 }
3915
3916         } else if (!status_update_file(view, &stage_status, stage_line_type)) {
3917                 report("Failed to update file");
3918                 return;
3919         }
3920
3921         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
3922
3923         view = VIEW(REQ_VIEW_STATUS);
3924         if (view_is_displayed(view))
3925                 status_enter(view, &view->line[view->lineno]);
3926 }
3927
3928 static enum request
3929 stage_request(struct view *view, enum request request, struct line *line)
3930 {
3931         switch (request) {
3932         case REQ_STATUS_UPDATE:
3933                 stage_update(view, line);
3934                 break;
3935
3936         case REQ_EDIT:
3937                 if (!stage_status.new.name[0])
3938                         return request;
3939
3940                 open_editor(stage_status.status != '?', stage_status.new.name);
3941                 break;
3942
3943         case REQ_ENTER:
3944                 pager_request(view, request, line);
3945                 break;
3946
3947         default:
3948                 return request;
3949         }
3950
3951         return REQ_NONE;
3952 }
3953
3954 static struct view_ops stage_ops = {
3955         "line",
3956         NULL,
3957         pager_read,
3958         pager_draw,
3959         stage_request,
3960         pager_grep,
3961         pager_select,
3962 };
3963
3964
3965 /*
3966  * Revision graph
3967  */
3968
3969 struct commit {
3970         char id[SIZEOF_REV];            /* SHA1 ID. */
3971         char title[128];                /* First line of the commit message. */
3972         char author[75];                /* Author of the commit. */
3973         struct tm time;                 /* Date from the author ident. */
3974         struct ref **refs;              /* Repository references. */
3975         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
3976         size_t graph_size;              /* The width of the graph array. */
3977 };
3978
3979 /* Size of rev graph with no  "padding" columns */
3980 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
3981
3982 struct rev_graph {
3983         struct rev_graph *prev, *next, *parents;
3984         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
3985         size_t size;
3986         struct commit *commit;
3987         size_t pos;
3988         unsigned int boundary:1;
3989 };
3990
3991 /* Parents of the commit being visualized. */
3992 static struct rev_graph graph_parents[4];
3993
3994 /* The current stack of revisions on the graph. */
3995 static struct rev_graph graph_stacks[4] = {
3996         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
3997         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
3998         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
3999         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
4000 };
4001
4002 static inline bool
4003 graph_parent_is_merge(struct rev_graph *graph)
4004 {
4005         return graph->parents->size > 1;
4006 }
4007
4008 static inline void
4009 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
4010 {
4011         struct commit *commit = graph->commit;
4012
4013         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
4014                 commit->graph[commit->graph_size++] = symbol;
4015 }
4016
4017 static void
4018 done_rev_graph(struct rev_graph *graph)
4019 {
4020         if (graph_parent_is_merge(graph) &&
4021             graph->pos < graph->size - 1 &&
4022             graph->next->size == graph->size + graph->parents->size - 1) {
4023                 size_t i = graph->pos + graph->parents->size - 1;
4024
4025                 graph->commit->graph_size = i * 2;
4026                 while (i < graph->next->size - 1) {
4027                         append_to_rev_graph(graph, ' ');
4028                         append_to_rev_graph(graph, '\\');
4029                         i++;
4030                 }
4031         }
4032
4033         graph->size = graph->pos = 0;
4034         graph->commit = NULL;
4035         memset(graph->parents, 0, sizeof(*graph->parents));
4036 }
4037
4038 static void
4039 push_rev_graph(struct rev_graph *graph, char *parent)
4040 {
4041         int i;
4042
4043         /* "Collapse" duplicate parents lines.
4044          *
4045          * FIXME: This needs to also update update the drawn graph but
4046          * for now it just serves as a method for pruning graph lines. */
4047         for (i = 0; i < graph->size; i++)
4048                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
4049                         return;
4050
4051         if (graph->size < SIZEOF_REVITEMS) {
4052                 string_copy_rev(graph->rev[graph->size++], parent);
4053         }
4054 }
4055
4056 static chtype
4057 get_rev_graph_symbol(struct rev_graph *graph)
4058 {
4059         chtype symbol;
4060
4061         if (graph->boundary)
4062                 symbol = REVGRAPH_BOUND;
4063         else if (graph->parents->size == 0)
4064                 symbol = REVGRAPH_INIT;
4065         else if (graph_parent_is_merge(graph))
4066                 symbol = REVGRAPH_MERGE;
4067         else if (graph->pos >= graph->size)
4068                 symbol = REVGRAPH_BRANCH;
4069         else
4070                 symbol = REVGRAPH_COMMIT;
4071
4072         return symbol;
4073 }
4074
4075 static void
4076 draw_rev_graph(struct rev_graph *graph)
4077 {
4078         struct rev_filler {
4079                 chtype separator, line;
4080         };
4081         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
4082         static struct rev_filler fillers[] = {
4083                 { ' ',  REVGRAPH_LINE },
4084                 { '`',  '.' },
4085                 { '\'', ' ' },
4086                 { '/',  ' ' },
4087         };
4088         chtype symbol = get_rev_graph_symbol(graph);
4089         struct rev_filler *filler;
4090         size_t i;
4091
4092         filler = &fillers[DEFAULT];
4093
4094         for (i = 0; i < graph->pos; i++) {
4095                 append_to_rev_graph(graph, filler->line);
4096                 if (graph_parent_is_merge(graph->prev) &&
4097                     graph->prev->pos == i)
4098                         filler = &fillers[RSHARP];
4099
4100                 append_to_rev_graph(graph, filler->separator);
4101         }
4102
4103         /* Place the symbol for this revision. */
4104         append_to_rev_graph(graph, symbol);
4105
4106         if (graph->prev->size > graph->size)
4107                 filler = &fillers[RDIAG];
4108         else
4109                 filler = &fillers[DEFAULT];
4110
4111         i++;
4112
4113         for (; i < graph->size; i++) {
4114                 append_to_rev_graph(graph, filler->separator);
4115                 append_to_rev_graph(graph, filler->line);
4116                 if (graph_parent_is_merge(graph->prev) &&
4117                     i < graph->prev->pos + graph->parents->size)
4118                         filler = &fillers[RSHARP];
4119                 if (graph->prev->size > graph->size)
4120                         filler = &fillers[LDIAG];
4121         }
4122
4123         if (graph->prev->size > graph->size) {
4124                 append_to_rev_graph(graph, filler->separator);
4125                 if (filler->line != ' ')
4126                         append_to_rev_graph(graph, filler->line);
4127         }
4128 }
4129
4130 /* Prepare the next rev graph */
4131 static void
4132 prepare_rev_graph(struct rev_graph *graph)
4133 {
4134         size_t i;
4135
4136         /* First, traverse all lines of revisions up to the active one. */
4137         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
4138                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
4139                         break;
4140
4141                 push_rev_graph(graph->next, graph->rev[graph->pos]);
4142         }
4143
4144         /* Interleave the new revision parent(s). */
4145         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
4146                 push_rev_graph(graph->next, graph->parents->rev[i]);
4147
4148         /* Lastly, put any remaining revisions. */
4149         for (i = graph->pos + 1; i < graph->size; i++)
4150                 push_rev_graph(graph->next, graph->rev[i]);
4151 }
4152
4153 static void
4154 update_rev_graph(struct rev_graph *graph)
4155 {
4156         /* If this is the finalizing update ... */
4157         if (graph->commit)
4158                 prepare_rev_graph(graph);
4159
4160         /* Graph visualization needs a one rev look-ahead,
4161          * so the first update doesn't visualize anything. */
4162         if (!graph->prev->commit)
4163                 return;
4164
4165         draw_rev_graph(graph->prev);
4166         done_rev_graph(graph->prev->prev);
4167 }
4168
4169
4170 /*
4171  * Main view backend
4172  */
4173
4174 static bool
4175 main_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
4176 {
4177         char buf[DATE_COLS + 1];
4178         struct commit *commit = line->data;
4179         enum line_type type;
4180         int col = 0;
4181         size_t timelen;
4182         int tilde_attr;
4183         int space;
4184
4185         if (!*commit->author)
4186                 return FALSE;
4187
4188         space = view->width;
4189         wmove(view->win, lineno, col);
4190
4191         if (selected) {
4192                 type = LINE_CURSOR;
4193                 wattrset(view->win, get_line_attr(type));
4194                 wchgat(view->win, -1, 0, type, NULL);
4195                 tilde_attr = -1;
4196         } else {
4197                 type = LINE_MAIN_COMMIT;
4198                 wattrset(view->win, get_line_attr(LINE_MAIN_DATE));
4199                 tilde_attr = get_line_attr(LINE_MAIN_DELIM);
4200         }
4201
4202         if (opt_date) {
4203                 int n;
4204
4205                 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time);
4206                 n = draw_text(
4207                         view, buf, view->width - col, col, FALSE, tilde_attr);
4208                 draw_text(
4209                         view, " ", view->width - col - n, col + n, FALSE,
4210                         tilde_attr);
4211
4212                 col += DATE_COLS;
4213                 wmove(view->win, lineno, col);
4214                 if (col >= view->width)
4215                         return TRUE;
4216         }
4217         if (type != LINE_CURSOR)
4218                 wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR));
4219
4220         if (opt_author) {
4221                 int max_len;
4222
4223                 max_len = view->width - col;
4224                 if (max_len > AUTHOR_COLS - 1)
4225                         max_len = AUTHOR_COLS - 1;
4226                 draw_text(
4227                         view, commit->author, max_len, col, TRUE, tilde_attr);
4228                 col += AUTHOR_COLS;
4229                 if (col >= view->width)
4230                         return TRUE;
4231         }
4232
4233         if (opt_rev_graph && commit->graph_size) {
4234                 size_t graph_size = view->width - col;
4235                 size_t i;
4236
4237                 if (type != LINE_CURSOR)
4238                         wattrset(view->win, get_line_attr(LINE_MAIN_REVGRAPH));
4239                 wmove(view->win, lineno, col);
4240                 if (graph_size > commit->graph_size)
4241                         graph_size = commit->graph_size;
4242                 /* Using waddch() instead of waddnstr() ensures that
4243                  * they'll be rendered correctly for the cursor line. */
4244                 for (i = 0; i < graph_size; i++)
4245                         waddch(view->win, commit->graph[i]);
4246
4247                 col += commit->graph_size + 1;
4248                 if (col >= view->width)
4249                         return TRUE;
4250                 waddch(view->win, ' ');
4251         }
4252         if (type != LINE_CURSOR)
4253                 wattrset(view->win, A_NORMAL);
4254
4255         wmove(view->win, lineno, col);
4256
4257         if (opt_show_refs && commit->refs) {
4258                 size_t i = 0;
4259
4260                 do {
4261                         if (type == LINE_CURSOR)
4262                                 ;
4263                         else if (commit->refs[i]->tag)
4264                                 wattrset(view->win, get_line_attr(LINE_MAIN_TAG));
4265                         else if (commit->refs[i]->remote)
4266                                 wattrset(view->win, get_line_attr(LINE_MAIN_REMOTE));
4267                         else
4268                                 wattrset(view->win, get_line_attr(LINE_MAIN_REF));
4269
4270                         col += draw_text(
4271                                 view, "[", view->width - col, col, TRUE,
4272                                 tilde_attr);
4273                         col += draw_text(
4274                                 view, commit->refs[i]->name, view->width - col,
4275                                 col, TRUE, tilde_attr);
4276                         col += draw_text(
4277                                 view, "]", view->width - col, col, TRUE,
4278                                 tilde_attr);
4279                         if (type != LINE_CURSOR)
4280                                 wattrset(view->win, A_NORMAL);
4281                         col += draw_text(
4282                                 view, " ", view->width - col, col, TRUE,
4283                                 tilde_attr);
4284                         if (col >= view->width)
4285                                 return TRUE;
4286                 } while (commit->refs[i++]->next);
4287         }
4288
4289         if (type != LINE_CURSOR)
4290                 wattrset(view->win, get_line_attr(type));
4291
4292         col += draw_text(
4293                 view, commit->title, view->width - col, col, TRUE, tilde_attr);
4294
4295         return TRUE;
4296 }
4297
4298 /* Reads git log --pretty=raw output and parses it into the commit struct. */
4299 static bool
4300 main_read(struct view *view, char *line)
4301 {
4302         static struct rev_graph *graph = graph_stacks;
4303         enum line_type type;
4304         struct commit *commit;
4305
4306         if (!line) {
4307                 update_rev_graph(graph);
4308                 return TRUE;
4309         }
4310
4311         type = get_line_type(line);
4312         if (type == LINE_COMMIT) {
4313                 commit = calloc(1, sizeof(struct commit));
4314                 if (!commit)
4315                         return FALSE;
4316
4317                 line += STRING_SIZE("commit ");
4318                 if (*line == '-') {
4319                         graph->boundary = 1;
4320                         line++;
4321                 }
4322
4323                 string_copy_rev(commit->id, line);
4324                 commit->refs = get_refs(commit->id);
4325                 graph->commit = commit;
4326                 add_line_data(view, commit, LINE_MAIN_COMMIT);
4327                 return TRUE;
4328         }
4329
4330         if (!view->lines)
4331                 return TRUE;
4332         commit = view->line[view->lines - 1].data;
4333
4334         switch (type) {
4335         case LINE_PARENT:
4336                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
4337                 break;
4338
4339         case LINE_AUTHOR:
4340         {
4341                 /* Parse author lines where the name may be empty:
4342                  *      author  <email@address.tld> 1138474660 +0100
4343                  */
4344                 char *ident = line + STRING_SIZE("author ");
4345                 char *nameend = strchr(ident, '<');
4346                 char *emailend = strchr(ident, '>');
4347
4348                 if (!nameend || !emailend)
4349                         break;
4350
4351                 update_rev_graph(graph);
4352                 graph = graph->next;
4353
4354                 *nameend = *emailend = 0;
4355                 ident = chomp_string(ident);
4356                 if (!*ident) {
4357                         ident = chomp_string(nameend + 1);
4358                         if (!*ident)
4359                                 ident = "Unknown";
4360                 }
4361
4362                 string_ncopy(commit->author, ident, strlen(ident));
4363
4364                 /* Parse epoch and timezone */
4365                 if (emailend[1] == ' ') {
4366                         char *secs = emailend + 2;
4367                         char *zone = strchr(secs, ' ');
4368                         time_t time = (time_t) atol(secs);
4369
4370                         if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
4371                                 long tz;
4372
4373                                 zone++;
4374                                 tz  = ('0' - zone[1]) * 60 * 60 * 10;
4375                                 tz += ('0' - zone[2]) * 60 * 60;
4376                                 tz += ('0' - zone[3]) * 60;
4377                                 tz += ('0' - zone[4]) * 60;
4378
4379                                 if (zone[0] == '-')
4380                                         tz = -tz;
4381
4382                                 time -= tz;
4383                         }
4384
4385                         gmtime_r(&time, &commit->time);
4386                 }
4387                 break;
4388         }
4389         default:
4390                 /* Fill in the commit title if it has not already been set. */
4391                 if (commit->title[0])
4392                         break;
4393
4394                 /* Require titles to start with a non-space character at the
4395                  * offset used by git log. */
4396                 if (strncmp(line, "    ", 4))
4397                         break;
4398                 line += 4;
4399                 /* Well, if the title starts with a whitespace character,
4400                  * try to be forgiving.  Otherwise we end up with no title. */
4401                 while (isspace(*line))
4402                         line++;
4403                 if (*line == '\0')
4404                         break;
4405                 /* FIXME: More graceful handling of titles; append "..." to
4406                  * shortened titles, etc. */
4407
4408                 string_ncopy(commit->title, line, strlen(line));
4409         }
4410
4411         return TRUE;
4412 }
4413
4414 static enum request
4415 main_request(struct view *view, enum request request, struct line *line)
4416 {
4417         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4418
4419         if (request == REQ_ENTER)
4420                 open_view(view, REQ_VIEW_DIFF, flags);
4421         else
4422                 return request;
4423
4424         return REQ_NONE;
4425 }
4426
4427 static bool
4428 main_grep(struct view *view, struct line *line)
4429 {
4430         struct commit *commit = line->data;
4431         enum { S_TITLE, S_AUTHOR, S_DATE, S_END } state;
4432         char buf[DATE_COLS + 1];
4433         regmatch_t pmatch;
4434
4435         for (state = S_TITLE; state < S_END; state++) {
4436                 char *text;
4437
4438                 switch (state) {
4439                 case S_TITLE:   text = commit->title;   break;
4440                 case S_AUTHOR:  text = commit->author;  break;
4441                 case S_DATE:
4442                         if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
4443                                 continue;
4444                         text = buf;
4445                         break;
4446
4447                 default:
4448                         return FALSE;
4449                 }
4450
4451                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4452                         return TRUE;
4453         }
4454
4455         return FALSE;
4456 }
4457
4458 static void
4459 main_select(struct view *view, struct line *line)
4460 {
4461         struct commit *commit = line->data;
4462
4463         string_copy_rev(view->ref, commit->id);
4464         string_copy_rev(ref_commit, view->ref);
4465 }
4466
4467 static struct view_ops main_ops = {
4468         "commit",
4469         NULL,
4470         main_read,
4471         main_draw,
4472         main_request,
4473         main_grep,
4474         main_select,
4475 };
4476
4477
4478 /*
4479  * Unicode / UTF-8 handling
4480  *
4481  * NOTE: Much of the following code for dealing with unicode is derived from
4482  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
4483  * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
4484  */
4485
4486 /* I've (over)annotated a lot of code snippets because I am not entirely
4487  * confident that the approach taken by this small UTF-8 interface is correct.
4488  * --jonas */
4489
4490 static inline int
4491 unicode_width(unsigned long c)
4492 {
4493         if (c >= 0x1100 &&
4494            (c <= 0x115f                         /* Hangul Jamo */
4495             || c == 0x2329
4496             || c == 0x232a
4497             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
4498                                                 /* CJK ... Yi */
4499             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
4500             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
4501             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
4502             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
4503             || (c >= 0xffe0  && c <= 0xffe6)
4504             || (c >= 0x20000 && c <= 0x2fffd)
4505             || (c >= 0x30000 && c <= 0x3fffd)))
4506                 return 2;
4507
4508         if (c == '\t')
4509                 return opt_tab_size;
4510
4511         return 1;
4512 }
4513
4514 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
4515  * Illegal bytes are set one. */
4516 static const unsigned char utf8_bytes[256] = {
4517         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,
4518         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,
4519         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,
4520         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,
4521         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,
4522         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,
4523         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,
4524         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,
4525 };
4526
4527 /* Decode UTF-8 multi-byte representation into a unicode character. */
4528 static inline unsigned long
4529 utf8_to_unicode(const char *string, size_t length)
4530 {
4531         unsigned long unicode;
4532
4533         switch (length) {
4534         case 1:
4535                 unicode  =   string[0];
4536                 break;
4537         case 2:
4538                 unicode  =  (string[0] & 0x1f) << 6;
4539                 unicode +=  (string[1] & 0x3f);
4540                 break;
4541         case 3:
4542                 unicode  =  (string[0] & 0x0f) << 12;
4543                 unicode += ((string[1] & 0x3f) << 6);
4544                 unicode +=  (string[2] & 0x3f);
4545                 break;
4546         case 4:
4547                 unicode  =  (string[0] & 0x0f) << 18;
4548                 unicode += ((string[1] & 0x3f) << 12);
4549                 unicode += ((string[2] & 0x3f) << 6);
4550                 unicode +=  (string[3] & 0x3f);
4551                 break;
4552         case 5:
4553                 unicode  =  (string[0] & 0x0f) << 24;
4554                 unicode += ((string[1] & 0x3f) << 18);
4555                 unicode += ((string[2] & 0x3f) << 12);
4556                 unicode += ((string[3] & 0x3f) << 6);
4557                 unicode +=  (string[4] & 0x3f);
4558                 break;
4559         case 6:
4560                 unicode  =  (string[0] & 0x01) << 30;
4561                 unicode += ((string[1] & 0x3f) << 24);
4562                 unicode += ((string[2] & 0x3f) << 18);
4563                 unicode += ((string[3] & 0x3f) << 12);
4564                 unicode += ((string[4] & 0x3f) << 6);
4565                 unicode +=  (string[5] & 0x3f);
4566                 break;
4567         default:
4568                 die("Invalid unicode length");
4569         }
4570
4571         /* Invalid characters could return the special 0xfffd value but NUL
4572          * should be just as good. */
4573         return unicode > 0xffff ? 0 : unicode;
4574 }
4575
4576 /* Calculates how much of string can be shown within the given maximum width
4577  * and sets trimmed parameter to non-zero value if all of string could not be
4578  * shown. If the reserve flag is TRUE, it will reserve at least one
4579  * trailing character, which can be useful when drawing a delimiter.
4580  *
4581  * Returns the number of bytes to output from string to satisfy max_width. */
4582 static size_t
4583 utf8_length(const char *string, size_t max_width, int *trimmed, bool reserve)
4584 {
4585         const char *start = string;
4586         const char *end = strchr(string, '\0');
4587         unsigned char last_bytes = 0;
4588         size_t width = 0;
4589
4590         *trimmed = 0;
4591
4592         while (string < end) {
4593                 int c = *(unsigned char *) string;
4594                 unsigned char bytes = utf8_bytes[c];
4595                 size_t ucwidth;
4596                 unsigned long unicode;
4597
4598                 if (string + bytes > end)
4599                         break;
4600
4601                 /* Change representation to figure out whether
4602                  * it is a single- or double-width character. */
4603
4604                 unicode = utf8_to_unicode(string, bytes);
4605                 /* FIXME: Graceful handling of invalid unicode character. */
4606                 if (!unicode)
4607                         break;
4608
4609                 ucwidth = unicode_width(unicode);
4610                 width  += ucwidth;
4611                 if (width > max_width) {
4612                         *trimmed = 1;
4613                         if (reserve && width - ucwidth == max_width) {
4614                                 string -= last_bytes;
4615                         }
4616                         break;
4617                 }
4618
4619                 string  += bytes;
4620                 last_bytes = bytes;
4621         }
4622
4623         return string - start;
4624 }
4625
4626
4627 /*
4628  * Status management
4629  */
4630
4631 /* Whether or not the curses interface has been initialized. */
4632 static bool cursed = FALSE;
4633
4634 /* The status window is used for polling keystrokes. */
4635 static WINDOW *status_win;
4636
4637 static bool status_empty = TRUE;
4638
4639 /* Update status and title window. */
4640 static void
4641 report(const char *msg, ...)
4642 {
4643         struct view *view = display[current_view];
4644
4645         if (input_mode)
4646                 return;
4647
4648         if (!view) {
4649                 char buf[SIZEOF_STR];
4650                 va_list args;
4651
4652                 va_start(args, msg);
4653                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
4654                         buf[sizeof(buf) - 1] = 0;
4655                         buf[sizeof(buf) - 2] = '.';
4656                         buf[sizeof(buf) - 3] = '.';
4657                         buf[sizeof(buf) - 4] = '.';
4658                 }
4659                 va_end(args);
4660                 die("%s", buf);
4661         }
4662
4663         if (!status_empty || *msg) {
4664                 va_list args;
4665
4666                 va_start(args, msg);
4667
4668                 wmove(status_win, 0, 0);
4669                 if (*msg) {
4670                         vwprintw(status_win, msg, args);
4671                         status_empty = FALSE;
4672                 } else {
4673                         status_empty = TRUE;
4674                 }
4675                 wclrtoeol(status_win);
4676                 wrefresh(status_win);
4677
4678                 va_end(args);
4679         }
4680
4681         update_view_title(view);
4682         update_display_cursor(view);
4683 }
4684
4685 /* Controls when nodelay should be in effect when polling user input. */
4686 static void
4687 set_nonblocking_input(bool loading)
4688 {
4689         static unsigned int loading_views;
4690
4691         if ((loading == FALSE && loading_views-- == 1) ||
4692             (loading == TRUE  && loading_views++ == 0))
4693                 nodelay(status_win, loading);
4694 }
4695
4696 static void
4697 init_display(void)
4698 {
4699         int x, y;
4700
4701         /* Initialize the curses library */
4702         if (isatty(STDIN_FILENO)) {
4703                 cursed = !!initscr();
4704         } else {
4705                 /* Leave stdin and stdout alone when acting as a pager. */
4706                 FILE *io = fopen("/dev/tty", "r+");
4707
4708                 if (!io)
4709                         die("Failed to open /dev/tty");
4710                 cursed = !!newterm(NULL, io, io);
4711         }
4712
4713         if (!cursed)
4714                 die("Failed to initialize curses");
4715
4716         nonl();         /* Tell curses not to do NL->CR/NL on output */
4717         cbreak();       /* Take input chars one at a time, no wait for \n */
4718         noecho();       /* Don't echo input */
4719         leaveok(stdscr, TRUE);
4720
4721         if (has_colors())
4722                 init_colors();
4723
4724         getmaxyx(stdscr, y, x);
4725         status_win = newwin(1, 0, y - 1, 0);
4726         if (!status_win)
4727                 die("Failed to create status window");
4728
4729         /* Enable keyboard mapping */
4730         keypad(status_win, TRUE);
4731         wbkgdset(status_win, get_line_attr(LINE_STATUS));
4732 }
4733
4734 static char *
4735 read_prompt(const char *prompt)
4736 {
4737         enum { READING, STOP, CANCEL } status = READING;
4738         static char buf[sizeof(opt_cmd) - STRING_SIZE("git \0")];
4739         int pos = 0;
4740
4741         while (status == READING) {
4742                 struct view *view;
4743                 int i, key;
4744
4745                 input_mode = TRUE;
4746
4747                 foreach_view (view, i)
4748                         update_view(view);
4749
4750                 input_mode = FALSE;
4751
4752                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
4753                 wclrtoeol(status_win);
4754
4755                 /* Refresh, accept single keystroke of input */
4756                 key = wgetch(status_win);
4757                 switch (key) {
4758                 case KEY_RETURN:
4759                 case KEY_ENTER:
4760                 case '\n':
4761                         status = pos ? STOP : CANCEL;
4762                         break;
4763
4764                 case KEY_BACKSPACE:
4765                         if (pos > 0)
4766                                 pos--;
4767                         else
4768                                 status = CANCEL;
4769                         break;
4770
4771                 case KEY_ESC:
4772                         status = CANCEL;
4773                         break;
4774
4775                 case ERR:
4776                         break;
4777
4778                 default:
4779                         if (pos >= sizeof(buf)) {
4780                                 report("Input string too long");
4781                                 return NULL;
4782                         }
4783
4784                         if (isprint(key))
4785                                 buf[pos++] = (char) key;
4786                 }
4787         }
4788
4789         /* Clear the status window */
4790         status_empty = FALSE;
4791         report("");
4792
4793         if (status == CANCEL)
4794                 return NULL;
4795
4796         buf[pos++] = 0;
4797
4798         return buf;
4799 }
4800
4801 /*
4802  * Repository references
4803  */
4804
4805 static struct ref *refs;
4806 static size_t refs_size;
4807
4808 /* Id <-> ref store */
4809 static struct ref ***id_refs;
4810 static size_t id_refs_size;
4811
4812 static struct ref **
4813 get_refs(char *id)
4814 {
4815         struct ref ***tmp_id_refs;
4816         struct ref **ref_list = NULL;
4817         size_t ref_list_size = 0;
4818         size_t i;
4819
4820         for (i = 0; i < id_refs_size; i++)
4821                 if (!strcmp(id, id_refs[i][0]->id))
4822                         return id_refs[i];
4823
4824         tmp_id_refs = realloc(id_refs, (id_refs_size + 1) * sizeof(*id_refs));
4825         if (!tmp_id_refs)
4826                 return NULL;
4827
4828         id_refs = tmp_id_refs;
4829
4830         for (i = 0; i < refs_size; i++) {
4831                 struct ref **tmp;
4832
4833                 if (strcmp(id, refs[i].id))
4834                         continue;
4835
4836                 tmp = realloc(ref_list, (ref_list_size + 1) * sizeof(*ref_list));
4837                 if (!tmp) {
4838                         if (ref_list)
4839                                 free(ref_list);
4840                         return NULL;
4841                 }
4842
4843                 ref_list = tmp;
4844                 if (ref_list_size > 0)
4845                         ref_list[ref_list_size - 1]->next = 1;
4846                 ref_list[ref_list_size] = &refs[i];
4847
4848                 /* XXX: The properties of the commit chains ensures that we can
4849                  * safely modify the shared ref. The repo references will
4850                  * always be similar for the same id. */
4851                 ref_list[ref_list_size]->next = 0;
4852                 ref_list_size++;
4853         }
4854
4855         if (ref_list)
4856                 id_refs[id_refs_size++] = ref_list;
4857
4858         return ref_list;
4859 }
4860
4861 static int
4862 read_ref(char *id, size_t idlen, char *name, size_t namelen)
4863 {
4864         struct ref *ref;
4865         bool tag = FALSE;
4866         bool remote = FALSE;
4867
4868         if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
4869                 /* Commits referenced by tags has "^{}" appended. */
4870                 if (name[namelen - 1] != '}')
4871                         return OK;
4872
4873                 while (namelen > 0 && name[namelen] != '^')
4874                         namelen--;
4875
4876                 tag = TRUE;
4877                 namelen -= STRING_SIZE("refs/tags/");
4878                 name    += STRING_SIZE("refs/tags/");
4879
4880         } else if (!strncmp(name, "refs/remotes/", STRING_SIZE("refs/remotes/"))) {
4881                 remote = TRUE;
4882                 namelen -= STRING_SIZE("refs/remotes/");
4883                 name    += STRING_SIZE("refs/remotes/");
4884
4885         } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
4886                 namelen -= STRING_SIZE("refs/heads/");
4887                 name    += STRING_SIZE("refs/heads/");
4888
4889         } else if (!strcmp(name, "HEAD")) {
4890                 return OK;
4891         }
4892
4893         refs = realloc(refs, sizeof(*refs) * (refs_size + 1));
4894         if (!refs)
4895                 return ERR;
4896
4897         ref = &refs[refs_size++];
4898         ref->name = malloc(namelen + 1);
4899         if (!ref->name)
4900                 return ERR;
4901
4902         strncpy(ref->name, name, namelen);
4903         ref->name[namelen] = 0;
4904         ref->tag = tag;
4905         ref->remote = remote;
4906         string_copy_rev(ref->id, id);
4907
4908         return OK;
4909 }
4910
4911 static int
4912 load_refs(void)
4913 {
4914         const char *cmd_env = getenv("TIG_LS_REMOTE");
4915         const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
4916
4917         return read_properties(popen(cmd, "r"), "\t", read_ref);
4918 }
4919
4920 static int
4921 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
4922 {
4923         if (!strcmp(name, "i18n.commitencoding"))
4924                 string_ncopy(opt_encoding, value, valuelen);
4925
4926         if (!strcmp(name, "core.editor"))
4927                 string_ncopy(opt_editor, value, valuelen);
4928
4929         return OK;
4930 }
4931
4932 static int
4933 load_repo_config(void)
4934 {
4935         return read_properties(popen(GIT_CONFIG " --list", "r"),
4936                                "=", read_repo_config_option);
4937 }
4938
4939 static int
4940 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
4941 {
4942         if (!opt_git_dir[0]) {
4943                 string_ncopy(opt_git_dir, name, namelen);
4944
4945         } else if (opt_is_inside_work_tree == -1) {
4946                 /* This can be 3 different values depending on the
4947                  * version of git being used. If git-rev-parse does not
4948                  * understand --is-inside-work-tree it will simply echo
4949                  * the option else either "true" or "false" is printed.
4950                  * Default to true for the unknown case. */
4951                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
4952
4953         } else {
4954                 string_ncopy(opt_cdup, name, namelen);
4955         }
4956
4957         return OK;
4958 }
4959
4960 /* XXX: The line outputted by "--show-cdup" can be empty so the option
4961  * must be the last one! */
4962 static int
4963 load_repo_info(void)
4964 {
4965         return read_properties(popen("git rev-parse --git-dir --is-inside-work-tree --show-cdup 2>/dev/null", "r"),
4966                                "=", read_repo_info);
4967 }
4968
4969 static int
4970 read_properties(FILE *pipe, const char *separators,
4971                 int (*read_property)(char *, size_t, char *, size_t))
4972 {
4973         char buffer[BUFSIZ];
4974         char *name;
4975         int state = OK;
4976
4977         if (!pipe)
4978                 return ERR;
4979
4980         while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
4981                 char *value;
4982                 size_t namelen;
4983                 size_t valuelen;
4984
4985                 name = chomp_string(name);
4986                 namelen = strcspn(name, separators);
4987
4988                 if (name[namelen]) {
4989                         name[namelen] = 0;
4990                         value = chomp_string(name + namelen + 1);
4991                         valuelen = strlen(value);
4992
4993                 } else {
4994                         value = "";
4995                         valuelen = 0;
4996                 }
4997
4998                 state = read_property(name, namelen, value, valuelen);
4999         }
5000
5001         if (state != ERR && ferror(pipe))
5002                 state = ERR;
5003
5004         pclose(pipe);
5005
5006         return state;
5007 }
5008
5009
5010 /*
5011  * Main
5012  */
5013
5014 static void __NORETURN
5015 quit(int sig)
5016 {
5017         /* XXX: Restore tty modes and let the OS cleanup the rest! */
5018         if (cursed)
5019                 endwin();
5020         exit(0);
5021 }
5022
5023 static void __NORETURN
5024 die(const char *err, ...)
5025 {
5026         va_list args;
5027
5028         endwin();
5029
5030         va_start(args, err);
5031         fputs("tig: ", stderr);
5032         vfprintf(stderr, err, args);
5033         fputs("\n", stderr);
5034         va_end(args);
5035
5036         exit(1);
5037 }
5038
5039 static void
5040 warn(const char *msg, ...)
5041 {
5042         va_list args;
5043
5044         va_start(args, msg);
5045         fputs("tig warning: ", stderr);
5046         vfprintf(stderr, msg, args);
5047         fputs("\n", stderr);
5048         va_end(args);
5049 }
5050
5051 int
5052 main(int argc, char *argv[])
5053 {
5054         struct view *view;
5055         enum request request;
5056         size_t i;
5057
5058         signal(SIGINT, quit);
5059
5060         if (setlocale(LC_ALL, "")) {
5061                 char *codeset = nl_langinfo(CODESET);
5062
5063                 string_ncopy(opt_codeset, codeset, strlen(codeset));
5064         }
5065
5066         if (load_repo_info() == ERR)
5067                 die("Failed to load repo info.");
5068
5069         if (load_options() == ERR)
5070                 die("Failed to load user config.");
5071
5072         /* Load the repo config file so options can be overwritten from
5073          * the command line. */
5074         if (load_repo_config() == ERR)
5075                 die("Failed to load repo config.");
5076
5077         if (!parse_options(argc, argv))
5078                 return 0;
5079
5080         /* Require a git repository unless when running in pager mode. */
5081         if (!opt_git_dir[0])
5082                 die("Not a git repository");
5083
5084         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
5085                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
5086                 if (opt_iconv == ICONV_NONE)
5087                         die("Failed to initialize character set conversion");
5088         }
5089
5090         if (load_refs() == ERR)
5091                 die("Failed to load refs.");
5092
5093         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
5094                 view->cmd_env = getenv(view->cmd_env);
5095
5096         request = opt_request;
5097
5098         init_display();
5099
5100         while (view_driver(display[current_view], request)) {
5101                 int key;
5102                 int i;
5103
5104                 foreach_view (view, i)
5105                         update_view(view);
5106
5107                 /* Refresh, accept single keystroke of input */
5108                 key = wgetch(status_win);
5109
5110                 /* wgetch() with nodelay() enabled returns ERR when there's no
5111                  * input. */
5112                 if (key == ERR) {
5113                         request = REQ_NONE;
5114                         continue;
5115                 }
5116
5117                 request = get_keybinding(display[current_view]->keymap, key);
5118
5119                 /* Some low-level request handling. This keeps access to
5120                  * status_win restricted. */
5121                 switch (request) {
5122                 case REQ_PROMPT:
5123                 {
5124                         char *cmd = read_prompt(":");
5125
5126                         if (cmd && string_format(opt_cmd, "git %s", cmd)) {
5127                                 if (strncmp(cmd, "show", 4) && isspace(cmd[4])) {
5128                                         opt_request = REQ_VIEW_DIFF;
5129                                 } else {
5130                                         opt_request = REQ_VIEW_PAGER;
5131                                 }
5132                                 break;
5133                         }
5134
5135                         request = REQ_NONE;
5136                         break;
5137                 }
5138                 case REQ_SEARCH:
5139                 case REQ_SEARCH_BACK:
5140                 {
5141                         const char *prompt = request == REQ_SEARCH
5142                                            ? "/" : "?";
5143                         char *search = read_prompt(prompt);
5144
5145                         if (search)
5146                                 string_ncopy(opt_search, search, strlen(search));
5147                         else
5148                                 request = REQ_NONE;
5149                         break;
5150                 }
5151                 case REQ_SCREEN_RESIZE:
5152                 {
5153                         int height, width;
5154
5155                         getmaxyx(stdscr, height, width);
5156
5157                         /* Resize the status view and let the view driver take
5158                          * care of resizing the displayed views. */
5159                         wresize(status_win, 1, width);
5160                         mvwin(status_win, height - 1, 0);
5161                         wrefresh(status_win);
5162                         break;
5163                 }
5164                 default:
5165                         break;
5166                 }
5167         }
5168
5169         quit(0);
5170
5171         return 0;
5172 }