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