Fixed handling of UTF8 tag names and commit messages.
[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 --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 textlen = strlen(text);
2641         int attr;
2642
2643         wmove(view->win, lineno, 0);
2644
2645         if (selected) {
2646                 type = LINE_CURSOR;
2647                 wchgat(view->win, -1, 0, type, NULL);
2648         }
2649
2650         attr = get_line_attr(type);
2651         wattrset(view->win, attr);
2652
2653         if (opt_line_number || opt_tab_size < TABSIZE) {
2654                 static char spaces[] = "                    ";
2655                 int col_offset = 0, col = 0;
2656
2657                 if (opt_line_number) {
2658                         unsigned long real_lineno = view->offset + lineno + 1;
2659
2660                         if (real_lineno == 1 ||
2661                             (real_lineno % opt_num_interval) == 0) {
2662                                 wprintw(view->win, "%.*d", view->digits, real_lineno);
2663
2664                         } else {
2665                                 waddnstr(view->win, spaces,
2666                                          MIN(view->digits, STRING_SIZE(spaces)));
2667                         }
2668                         waddstr(view->win, ": ");
2669                         col_offset = view->digits + 2;
2670                 }
2671
2672                 while (text && col_offset + col < view->width) {
2673                         int cols_max = view->width - col_offset - col;
2674                         char *pos = text;
2675                         int cols;
2676
2677                         if (*text == '\t') {
2678                                 text++;
2679                                 assert(sizeof(spaces) > TABSIZE);
2680                                 pos = spaces;
2681                                 cols = opt_tab_size - (col % opt_tab_size);
2682
2683                         } else {
2684                                 text = strchr(text, '\t');
2685                                 cols = line ? text - pos : strlen(pos);
2686                         }
2687
2688                         waddnstr(view->win, pos, MIN(cols, cols_max));
2689                         col += cols;
2690                 }
2691
2692         } else {
2693                 int col = 0, pos = 0;
2694
2695                 for (; pos < textlen && col < view->width; pos++, col++)
2696                         if (text[pos] == '\t')
2697                                 col += TABSIZE - (col % TABSIZE) - 1;
2698
2699                 waddnstr(view->win, text, pos);
2700         }
2701
2702         return TRUE;
2703 }
2704
2705 static bool
2706 add_describe_ref(char *buf, size_t *bufpos, char *commit_id, const char *sep)
2707 {
2708         char refbuf[SIZEOF_STR];
2709         char *ref = NULL;
2710         FILE *pipe;
2711
2712         if (!string_format(refbuf, "git describe %s 2>/dev/null", commit_id))
2713                 return TRUE;
2714
2715         pipe = popen(refbuf, "r");
2716         if (!pipe)
2717                 return TRUE;
2718
2719         if ((ref = fgets(refbuf, sizeof(refbuf), pipe)))
2720                 ref = chomp_string(ref);
2721         pclose(pipe);
2722
2723         if (!ref || !*ref)
2724                 return TRUE;
2725
2726         /* This is the only fatal call, since it can "corrupt" the buffer. */
2727         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
2728                 return FALSE;
2729
2730         return TRUE;
2731 }
2732
2733 static void
2734 add_pager_refs(struct view *view, struct line *line)
2735 {
2736         char buf[SIZEOF_STR];
2737         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
2738         struct ref **refs;
2739         size_t bufpos = 0, refpos = 0;
2740         const char *sep = "Refs: ";
2741         bool is_tag = FALSE;
2742
2743         assert(line->type == LINE_COMMIT);
2744
2745         refs = get_refs(commit_id);
2746         if (!refs) {
2747                 if (view == VIEW(REQ_VIEW_DIFF))
2748                         goto try_add_describe_ref;
2749                 return;
2750         }
2751
2752         do {
2753                 struct ref *ref = refs[refpos];
2754                 char *fmt = ref->tag    ? "%s[%s]" :
2755                             ref->remote ? "%s<%s>" : "%s%s";
2756
2757                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
2758                         return;
2759                 sep = ", ";
2760                 if (ref->tag)
2761                         is_tag = TRUE;
2762         } while (refs[refpos++]->next);
2763
2764         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
2765 try_add_describe_ref:
2766                 /* Add <tag>-g<commit_id> "fake" reference. */
2767                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
2768                         return;
2769         }
2770
2771         if (bufpos == 0)
2772                 return;
2773
2774         if (!realloc_lines(view, view->line_size + 1))
2775                 return;
2776
2777         add_line_text(view, buf, LINE_PP_REFS);
2778 }
2779
2780 static bool
2781 pager_read(struct view *view, char *data)
2782 {
2783         struct line *line;
2784
2785         if (!data)
2786                 return TRUE;
2787
2788         line = add_line_text(view, data, get_line_type(data));
2789         if (!line)
2790                 return FALSE;
2791
2792         if (line->type == LINE_COMMIT &&
2793             (view == VIEW(REQ_VIEW_DIFF) ||
2794              view == VIEW(REQ_VIEW_LOG)))
2795                 add_pager_refs(view, line);
2796
2797         return TRUE;
2798 }
2799
2800 static enum request
2801 pager_request(struct view *view, enum request request, struct line *line)
2802 {
2803         int split = 0;
2804
2805         if (request != REQ_ENTER)
2806                 return request;
2807
2808         if (line->type == LINE_COMMIT &&
2809            (view == VIEW(REQ_VIEW_LOG) ||
2810             view == VIEW(REQ_VIEW_PAGER))) {
2811                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
2812                 split = 1;
2813         }
2814
2815         /* Always scroll the view even if it was split. That way
2816          * you can use Enter to scroll through the log view and
2817          * split open each commit diff. */
2818         scroll_view(view, REQ_SCROLL_LINE_DOWN);
2819
2820         /* FIXME: A minor workaround. Scrolling the view will call report("")
2821          * but if we are scrolling a non-current view this won't properly
2822          * update the view title. */
2823         if (split)
2824                 update_view_title(view);
2825
2826         return REQ_NONE;
2827 }
2828
2829 static bool
2830 pager_grep(struct view *view, struct line *line)
2831 {
2832         regmatch_t pmatch;
2833         char *text = line->data;
2834
2835         if (!*text)
2836                 return FALSE;
2837
2838         if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
2839                 return FALSE;
2840
2841         return TRUE;
2842 }
2843
2844 static void
2845 pager_select(struct view *view, struct line *line)
2846 {
2847         if (line->type == LINE_COMMIT) {
2848                 char *text = (char *)line->data + STRING_SIZE("commit ");
2849
2850                 if (view != VIEW(REQ_VIEW_PAGER))
2851                         string_copy_rev(view->ref, text);
2852                 string_copy_rev(ref_commit, text);
2853         }
2854 }
2855
2856 static struct view_ops pager_ops = {
2857         "line",
2858         NULL,
2859         pager_read,
2860         pager_draw,
2861         pager_request,
2862         pager_grep,
2863         pager_select,
2864 };
2865
2866
2867 /*
2868  * Help backend
2869  */
2870
2871 static bool
2872 help_open(struct view *view)
2873 {
2874         char buf[BUFSIZ];
2875         int lines = ARRAY_SIZE(req_info) + 2;
2876         int i;
2877
2878         if (view->lines > 0)
2879                 return TRUE;
2880
2881         for (i = 0; i < ARRAY_SIZE(req_info); i++)
2882                 if (!req_info[i].request)
2883                         lines++;
2884
2885         lines += run_requests + 1;
2886
2887         view->line = calloc(lines, sizeof(*view->line));
2888         if (!view->line)
2889                 return FALSE;
2890
2891         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
2892
2893         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
2894                 char *key;
2895
2896                 if (req_info[i].request == REQ_NONE)
2897                         continue;
2898
2899                 if (!req_info[i].request) {
2900                         add_line_text(view, "", LINE_DEFAULT);
2901                         add_line_text(view, req_info[i].help, LINE_DEFAULT);
2902                         continue;
2903                 }
2904
2905                 key = get_key(req_info[i].request);
2906                 if (!*key)
2907                         key = "(no key defined)";
2908
2909                 if (!string_format(buf, "    %-25s %s", key, req_info[i].help))
2910                         continue;
2911
2912                 add_line_text(view, buf, LINE_DEFAULT);
2913         }
2914
2915         if (run_requests) {
2916                 add_line_text(view, "", LINE_DEFAULT);
2917                 add_line_text(view, "External commands:", LINE_DEFAULT);
2918         }
2919
2920         for (i = 0; i < run_requests; i++) {
2921                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
2922                 char *key;
2923
2924                 if (!req)
2925                         continue;
2926
2927                 key = get_key_name(req->key);
2928                 if (!*key)
2929                         key = "(no key defined)";
2930
2931                 if (!string_format(buf, "    %-10s %-14s `%s`",
2932                                    keymap_table[req->keymap].name,
2933                                    key, req->cmd))
2934                         continue;
2935
2936                 add_line_text(view, buf, LINE_DEFAULT);
2937         }
2938
2939         return TRUE;
2940 }
2941
2942 static struct view_ops help_ops = {
2943         "line",
2944         help_open,
2945         NULL,
2946         pager_draw,
2947         pager_request,
2948         pager_grep,
2949         pager_select,
2950 };
2951
2952
2953 /*
2954  * Tree backend
2955  */
2956
2957 struct tree_stack_entry {
2958         struct tree_stack_entry *prev;  /* Entry below this in the stack */
2959         unsigned long lineno;           /* Line number to restore */
2960         char *name;                     /* Position of name in opt_path */
2961 };
2962
2963 /* The top of the path stack. */
2964 static struct tree_stack_entry *tree_stack = NULL;
2965 unsigned long tree_lineno = 0;
2966
2967 static void
2968 pop_tree_stack_entry(void)
2969 {
2970         struct tree_stack_entry *entry = tree_stack;
2971
2972         tree_lineno = entry->lineno;
2973         entry->name[0] = 0;
2974         tree_stack = entry->prev;
2975         free(entry);
2976 }
2977
2978 static void
2979 push_tree_stack_entry(char *name, unsigned long lineno)
2980 {
2981         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
2982         size_t pathlen = strlen(opt_path);
2983
2984         if (!entry)
2985                 return;
2986
2987         entry->prev = tree_stack;
2988         entry->name = opt_path + pathlen;
2989         tree_stack = entry;
2990
2991         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
2992                 pop_tree_stack_entry();
2993                 return;
2994         }
2995
2996         /* Move the current line to the first tree entry. */
2997         tree_lineno = 1;
2998         entry->lineno = lineno;
2999 }
3000
3001 /* Parse output from git-ls-tree(1):
3002  *
3003  * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3004  * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3005  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3006  * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3007  */
3008
3009 #define SIZEOF_TREE_ATTR \
3010         STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3011
3012 #define TREE_UP_FORMAT "040000 tree %s\t.."
3013
3014 static int
3015 tree_compare_entry(enum line_type type1, char *name1,
3016                    enum line_type type2, char *name2)
3017 {
3018         if (type1 != type2) {
3019                 if (type1 == LINE_TREE_DIR)
3020                         return -1;
3021                 return 1;
3022         }
3023
3024         return strcmp(name1, name2);
3025 }
3026
3027 static bool
3028 tree_read(struct view *view, char *text)
3029 {
3030         size_t textlen = text ? strlen(text) : 0;
3031         char buf[SIZEOF_STR];
3032         unsigned long pos;
3033         enum line_type type;
3034         bool first_read = view->lines == 0;
3035
3036         if (textlen <= SIZEOF_TREE_ATTR)
3037                 return FALSE;
3038
3039         type = text[STRING_SIZE("100644 ")] == 't'
3040              ? LINE_TREE_DIR : LINE_TREE_FILE;
3041
3042         if (first_read) {
3043                 /* Add path info line */
3044                 if (!string_format(buf, "Directory path /%s", opt_path) ||
3045                     !realloc_lines(view, view->line_size + 1) ||
3046                     !add_line_text(view, buf, LINE_DEFAULT))
3047                         return FALSE;
3048
3049                 /* Insert "link" to parent directory. */
3050                 if (*opt_path) {
3051                         if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
3052                             !realloc_lines(view, view->line_size + 1) ||
3053                             !add_line_text(view, buf, LINE_TREE_DIR))
3054                                 return FALSE;
3055                 }
3056         }
3057
3058         /* Strip the path part ... */
3059         if (*opt_path) {
3060                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3061                 size_t striplen = strlen(opt_path);
3062                 char *path = text + SIZEOF_TREE_ATTR;
3063
3064                 if (pathlen > striplen)
3065                         memmove(path, path + striplen,
3066                                 pathlen - striplen + 1);
3067         }
3068
3069         /* Skip "Directory ..." and ".." line. */
3070         for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
3071                 struct line *line = &view->line[pos];
3072                 char *path1 = ((char *) line->data) + SIZEOF_TREE_ATTR;
3073                 char *path2 = text + SIZEOF_TREE_ATTR;
3074                 int cmp = tree_compare_entry(line->type, path1, type, path2);
3075
3076                 if (cmp <= 0)
3077                         continue;
3078
3079                 text = strdup(text);
3080                 if (!text)
3081                         return FALSE;
3082
3083                 if (view->lines > pos)
3084                         memmove(&view->line[pos + 1], &view->line[pos],
3085                                 (view->lines - pos) * sizeof(*line));
3086
3087                 line = &view->line[pos];
3088                 line->data = text;
3089                 line->type = type;
3090                 view->lines++;
3091                 return TRUE;
3092         }
3093
3094         if (!add_line_text(view, text, type))
3095                 return FALSE;
3096
3097         if (tree_lineno > view->lineno) {
3098                 view->lineno = tree_lineno;
3099                 tree_lineno = 0;
3100         }
3101
3102         return TRUE;
3103 }
3104
3105 static enum request
3106 tree_request(struct view *view, enum request request, struct line *line)
3107 {
3108         enum open_flags flags;
3109
3110         if (request == REQ_TREE_PARENT) {
3111                 if (*opt_path) {
3112                         /* fake 'cd  ..' */
3113                         request = REQ_ENTER;
3114                         line = &view->line[1];
3115                 } else {
3116                         /* quit view if at top of tree */
3117                         return REQ_VIEW_CLOSE;
3118                 }
3119         }
3120         if (request != REQ_ENTER)
3121                 return request;
3122
3123         /* Cleanup the stack if the tree view is at a different tree. */
3124         while (!*opt_path && tree_stack)
3125                 pop_tree_stack_entry();
3126
3127         switch (line->type) {
3128         case LINE_TREE_DIR:
3129                 /* Depending on whether it is a subdir or parent (updir?) link
3130                  * mangle the path buffer. */
3131                 if (line == &view->line[1] && *opt_path) {
3132                         pop_tree_stack_entry();
3133
3134                 } else {
3135                         char *data = line->data;
3136                         char *basename = data + SIZEOF_TREE_ATTR;
3137
3138                         push_tree_stack_entry(basename, view->lineno);
3139                 }
3140
3141                 /* Trees and subtrees share the same ID, so they are not not
3142                  * unique like blobs. */
3143                 flags = OPEN_RELOAD;
3144                 request = REQ_VIEW_TREE;
3145                 break;
3146
3147         case LINE_TREE_FILE:
3148                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3149                 request = REQ_VIEW_BLOB;
3150                 break;
3151
3152         default:
3153                 return TRUE;
3154         }
3155
3156         open_view(view, request, flags);
3157         if (request == REQ_VIEW_TREE) {
3158                 view->lineno = tree_lineno;
3159         }
3160
3161         return REQ_NONE;
3162 }
3163
3164 static void
3165 tree_select(struct view *view, struct line *line)
3166 {
3167         char *text = (char *)line->data + STRING_SIZE("100644 blob ");
3168
3169         if (line->type == LINE_TREE_FILE) {
3170                 string_copy_rev(ref_blob, text);
3171
3172         } else if (line->type != LINE_TREE_DIR) {
3173                 return;
3174         }
3175
3176         string_copy_rev(view->ref, text);
3177 }
3178
3179 static struct view_ops tree_ops = {
3180         "file",
3181         NULL,
3182         tree_read,
3183         pager_draw,
3184         tree_request,
3185         pager_grep,
3186         tree_select,
3187 };
3188
3189 static bool
3190 blob_read(struct view *view, char *line)
3191 {
3192         return add_line_text(view, line, LINE_DEFAULT) != NULL;
3193 }
3194
3195 static struct view_ops blob_ops = {
3196         "line",
3197         NULL,
3198         blob_read,
3199         pager_draw,
3200         pager_request,
3201         pager_grep,
3202         pager_select,
3203 };
3204
3205
3206 /*
3207  * Status backend
3208  */
3209
3210 struct status {
3211         char status;
3212         struct {
3213                 mode_t mode;
3214                 char rev[SIZEOF_REV];
3215                 char name[SIZEOF_STR];
3216         } old;
3217         struct {
3218                 mode_t mode;
3219                 char rev[SIZEOF_REV];
3220                 char name[SIZEOF_STR];
3221         } new;
3222 };
3223
3224 static struct status stage_status;
3225 static enum line_type stage_line_type;
3226
3227 /* Get fields from the diff line:
3228  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
3229  */
3230 static inline bool
3231 status_get_diff(struct status *file, char *buf, size_t bufsize)
3232 {
3233         char *old_mode = buf +  1;
3234         char *new_mode = buf +  8;
3235         char *old_rev  = buf + 15;
3236         char *new_rev  = buf + 56;
3237         char *status   = buf + 97;
3238
3239         if (bufsize < 99 ||
3240             old_mode[-1] != ':' ||
3241             new_mode[-1] != ' ' ||
3242             old_rev[-1]  != ' ' ||
3243             new_rev[-1]  != ' ' ||
3244             status[-1]   != ' ')
3245                 return FALSE;
3246
3247         file->status = *status;
3248
3249         string_copy_rev(file->old.rev, old_rev);
3250         string_copy_rev(file->new.rev, new_rev);
3251
3252         file->old.mode = strtoul(old_mode, NULL, 8);
3253         file->new.mode = strtoul(new_mode, NULL, 8);
3254
3255         file->old.name[0] = file->new.name[0] = 0;
3256
3257         return TRUE;
3258 }
3259
3260 static bool
3261 status_run(struct view *view, const char cmd[], bool diff, enum line_type type)
3262 {
3263         struct status *file = NULL;
3264         struct status *unmerged = NULL;
3265         char buf[SIZEOF_STR * 4];
3266         size_t bufsize = 0;
3267         FILE *pipe;
3268
3269         pipe = popen(cmd, "r");
3270         if (!pipe)
3271                 return FALSE;
3272
3273         add_line_data(view, NULL, type);
3274
3275         while (!feof(pipe) && !ferror(pipe)) {
3276                 char *sep;
3277                 size_t readsize;
3278
3279                 readsize = fread(buf + bufsize, 1, sizeof(buf) - bufsize, pipe);
3280                 if (!readsize)
3281                         break;
3282                 bufsize += readsize;
3283
3284                 /* Process while we have NUL chars. */
3285                 while ((sep = memchr(buf, 0, bufsize))) {
3286                         size_t sepsize = sep - buf + 1;
3287
3288                         if (!file) {
3289                                 if (!realloc_lines(view, view->line_size + 1))
3290                                         goto error_out;
3291
3292                                 file = calloc(1, sizeof(*file));
3293                                 if (!file)
3294                                         goto error_out;
3295
3296                                 add_line_data(view, file, type);
3297                         }
3298
3299                         /* Parse diff info part. */
3300                         if (!diff) {
3301                                 file->status = '?';
3302
3303                         } else if (!file->status) {
3304                                 if (!status_get_diff(file, buf, sepsize))
3305                                         goto error_out;
3306
3307                                 bufsize -= sepsize;
3308                                 memmove(buf, sep + 1, bufsize);
3309
3310                                 sep = memchr(buf, 0, bufsize);
3311                                 if (!sep)
3312                                         break;
3313                                 sepsize = sep - buf + 1;
3314
3315                                 /* Collapse all 'M'odified entries that
3316                                  * follow a associated 'U'nmerged entry.
3317                                  */
3318                                 if (file->status == 'U') {
3319                                         unmerged = file;
3320
3321                                 } else if (unmerged) {
3322                                         int collapse = !strcmp(buf, unmerged->new.name);
3323
3324                                         unmerged = NULL;
3325                                         if (collapse) {
3326                                                 free(file);
3327                                                 view->lines--;
3328                                                 continue;
3329                                         }
3330                                 }
3331                         }
3332
3333                         /* Grab the old name for rename/copy. */
3334                         if (!*file->old.name &&
3335                             (file->status == 'R' || file->status == 'C')) {
3336                                 sepsize = sep - buf + 1;
3337                                 string_ncopy(file->old.name, buf, sepsize);
3338                                 bufsize -= sepsize;
3339                                 memmove(buf, sep + 1, bufsize);
3340
3341                                 sep = memchr(buf, 0, bufsize);
3342                                 if (!sep)
3343                                         break;
3344                                 sepsize = sep - buf + 1;
3345                         }
3346
3347                         /* git-ls-files just delivers a NUL separated
3348                          * list of file names similar to the second half
3349                          * of the git-diff-* output. */
3350                         string_ncopy(file->new.name, buf, sepsize);
3351                         if (!*file->old.name)
3352                                 string_copy(file->old.name, file->new.name);
3353                         bufsize -= sepsize;
3354                         memmove(buf, sep + 1, bufsize);
3355                         file = NULL;
3356                 }
3357         }
3358
3359         if (ferror(pipe)) {
3360 error_out:
3361                 pclose(pipe);
3362                 return FALSE;
3363         }
3364
3365         if (!view->line[view->lines - 1].data)
3366                 add_line_data(view, NULL, LINE_STAT_NONE);
3367
3368         pclose(pipe);
3369         return TRUE;
3370 }
3371
3372 /* Don't show unmerged entries in the staged section. */
3373 #define STATUS_DIFF_INDEX_CMD "git diff-index -z --diff-filter=ACDMRTXB --cached -M HEAD"
3374 #define STATUS_DIFF_FILES_CMD "git update-index -q --refresh && git diff-files -z"
3375 #define STATUS_LIST_OTHER_CMD \
3376         "git ls-files -z --others --exclude-per-directory=.gitignore"
3377
3378 #define STATUS_DIFF_INDEX_SHOW_CMD \
3379         "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s %s 2>/dev/null"
3380
3381 #define STATUS_DIFF_FILES_SHOW_CMD \
3382         "git diff-files --root --patch-with-stat -C -M -- %s %s 2>/dev/null"
3383
3384 /* First parse staged info using git-diff-index(1), then parse unstaged
3385  * info using git-diff-files(1), and finally untracked files using
3386  * git-ls-files(1). */
3387 static bool
3388 status_open(struct view *view)
3389 {
3390         struct stat statbuf;
3391         char exclude[SIZEOF_STR];
3392         char cmd[SIZEOF_STR];
3393         unsigned long prev_lineno = view->lineno;
3394         size_t i;
3395
3396         for (i = 0; i < view->lines; i++)
3397                 free(view->line[i].data);
3398         free(view->line);
3399         view->lines = view->line_size = view->lineno = 0;
3400         view->line = NULL;
3401
3402         if (!realloc_lines(view, view->line_size + 6))
3403                 return FALSE;
3404
3405         if (!string_format(exclude, "%s/info/exclude", opt_git_dir))
3406                 return FALSE;
3407
3408         string_copy(cmd, STATUS_LIST_OTHER_CMD);
3409
3410         if (stat(exclude, &statbuf) >= 0) {
3411                 size_t cmdsize = strlen(cmd);
3412
3413                 if (!string_format_from(cmd, &cmdsize, " %s", "--exclude-from=") ||
3414                     sq_quote(cmd, cmdsize, exclude) >= sizeof(cmd))
3415                         return FALSE;
3416         }
3417
3418         if (!status_run(view, STATUS_DIFF_INDEX_CMD, TRUE, LINE_STAT_STAGED) ||
3419             !status_run(view, STATUS_DIFF_FILES_CMD, TRUE, LINE_STAT_UNSTAGED) ||
3420             !status_run(view, cmd, FALSE, LINE_STAT_UNTRACKED))
3421                 return FALSE;
3422
3423         /* If all went well restore the previous line number to stay in
3424          * the context. */
3425         if (prev_lineno < view->lines)
3426                 view->lineno = prev_lineno;
3427         else
3428                 view->lineno = view->lines - 1;
3429
3430         return TRUE;
3431 }
3432
3433 static bool
3434 status_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
3435 {
3436         struct status *status = line->data;
3437
3438         wmove(view->win, lineno, 0);
3439
3440         if (selected) {
3441                 wattrset(view->win, get_line_attr(LINE_CURSOR));
3442                 wchgat(view->win, -1, 0, LINE_CURSOR, NULL);
3443
3444         } else if (!status && line->type != LINE_STAT_NONE) {
3445                 wattrset(view->win, get_line_attr(LINE_STAT_SECTION));
3446                 wchgat(view->win, -1, 0, LINE_STAT_SECTION, NULL);
3447
3448         } else {
3449                 wattrset(view->win, get_line_attr(line->type));
3450         }
3451
3452         if (!status) {
3453                 char *text;
3454
3455                 switch (line->type) {
3456                 case LINE_STAT_STAGED:
3457                         text = "Changes to be committed:";
3458                         break;
3459
3460                 case LINE_STAT_UNSTAGED:
3461                         text = "Changed but not updated:";
3462                         break;
3463
3464                 case LINE_STAT_UNTRACKED:
3465                         text = "Untracked files:";
3466                         break;
3467
3468                 case LINE_STAT_NONE:
3469                         text = "    (no files)";
3470                         break;
3471
3472                 default:
3473                         return FALSE;
3474                 }
3475
3476                 waddstr(view->win, text);
3477                 return TRUE;
3478         }
3479
3480         waddch(view->win, status->status);
3481         if (!selected)
3482                 wattrset(view->win, A_NORMAL);
3483         wmove(view->win, lineno, 4);
3484         waddstr(view->win, status->new.name);
3485
3486         return TRUE;
3487 }
3488
3489 static enum request
3490 status_enter(struct view *view, struct line *line)
3491 {
3492         struct status *status = line->data;
3493         char oldpath[SIZEOF_STR] = "";
3494         char newpath[SIZEOF_STR] = "";
3495         char *info;
3496         size_t cmdsize = 0;
3497
3498         if (line->type == LINE_STAT_NONE ||
3499             (!status && line[1].type == LINE_STAT_NONE)) {
3500                 report("No file to diff");
3501                 return REQ_NONE;
3502         }
3503
3504         if (status) {
3505                 if (sq_quote(oldpath, 0, status->old.name) >= sizeof(oldpath))
3506                         return REQ_QUIT;
3507                 /* Diffs for unmerged entries are empty when pasing the
3508                  * new path, so leave it empty. */
3509                 if (status->status != 'U' &&
3510                     sq_quote(newpath, 0, status->new.name) >= sizeof(newpath))
3511                         return REQ_QUIT;
3512         }
3513
3514         if (opt_cdup[0] &&
3515             line->type != LINE_STAT_UNTRACKED &&
3516             !string_format_from(opt_cmd, &cmdsize, "cd %s;", opt_cdup))
3517                 return REQ_QUIT;
3518
3519         switch (line->type) {
3520         case LINE_STAT_STAGED:
3521                 if (!string_format_from(opt_cmd, &cmdsize,
3522                                         STATUS_DIFF_INDEX_SHOW_CMD, oldpath, newpath))
3523                         return REQ_QUIT;
3524                 if (status)
3525                         info = "Staged changes to %s";
3526                 else
3527                         info = "Staged changes";
3528                 break;
3529
3530         case LINE_STAT_UNSTAGED:
3531                 if (!string_format_from(opt_cmd, &cmdsize,
3532                                         STATUS_DIFF_FILES_SHOW_CMD, oldpath, newpath))
3533                         return REQ_QUIT;
3534                 if (status)
3535                         info = "Unstaged changes to %s";
3536                 else
3537                         info = "Unstaged changes";
3538                 break;
3539
3540         case LINE_STAT_UNTRACKED:
3541                 if (opt_pipe)
3542                         return REQ_QUIT;
3543
3544
3545                 if (!status) {
3546                         report("No file to show");
3547                         return REQ_NONE;
3548                 }
3549
3550                 opt_pipe = fopen(status->new.name, "r");
3551                 info = "Untracked file %s";
3552                 break;
3553
3554         default:
3555                 die("line type %d not handled in switch", line->type);
3556         }
3557
3558         open_view(view, REQ_VIEW_STAGE, OPEN_RELOAD | OPEN_SPLIT);
3559         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
3560                 if (status) {
3561                         stage_status = *status;
3562                 } else {
3563                         memset(&stage_status, 0, sizeof(stage_status));
3564                 }
3565
3566                 stage_line_type = line->type;
3567                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
3568         }
3569
3570         return REQ_NONE;
3571 }
3572
3573
3574 static bool
3575 status_update_file(struct view *view, struct status *status, enum line_type type)
3576 {
3577         char cmd[SIZEOF_STR];
3578         char buf[SIZEOF_STR];
3579         size_t cmdsize = 0;
3580         size_t bufsize = 0;
3581         size_t written = 0;
3582         FILE *pipe;
3583
3584         if (opt_cdup[0] &&
3585             type != LINE_STAT_UNTRACKED &&
3586             !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
3587                 return FALSE;
3588
3589         switch (type) {
3590         case LINE_STAT_STAGED:
3591                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
3592                                         status->old.mode,
3593                                         status->old.rev,
3594                                         status->old.name, 0))
3595                         return FALSE;
3596
3597                 string_add(cmd, cmdsize, "git update-index -z --index-info");
3598                 break;
3599
3600         case LINE_STAT_UNSTAGED:
3601         case LINE_STAT_UNTRACKED:
3602                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
3603                         return FALSE;
3604
3605                 string_add(cmd, cmdsize, "git update-index -z --add --remove --stdin");
3606                 break;
3607
3608         default:
3609                 die("line type %d not handled in switch", type);
3610         }
3611
3612         pipe = popen(cmd, "w");
3613         if (!pipe)
3614                 return FALSE;
3615
3616         while (!ferror(pipe) && written < bufsize) {
3617                 written += fwrite(buf + written, 1, bufsize - written, pipe);
3618         }
3619
3620         pclose(pipe);
3621
3622         if (written != bufsize)
3623                 return FALSE;
3624
3625         return TRUE;
3626 }
3627
3628 static void
3629 status_update(struct view *view)
3630 {
3631         struct line *line = &view->line[view->lineno];
3632
3633         assert(view->lines);
3634
3635         if (!line->data) {
3636                 while (++line < view->line + view->lines && line->data) {
3637                         if (!status_update_file(view, line->data, line->type))
3638                                 report("Failed to update file status");
3639                 }
3640
3641                 if (!line[-1].data) {
3642                         report("Nothing to update");
3643                         return;
3644                 }
3645
3646         } else if (!status_update_file(view, line->data, line->type)) {
3647                 report("Failed to update file status");
3648         }
3649 }
3650
3651 static enum request
3652 status_request(struct view *view, enum request request, struct line *line)
3653 {
3654         struct status *status = line->data;
3655
3656         switch (request) {
3657         case REQ_STATUS_UPDATE:
3658                 status_update(view);
3659                 break;
3660
3661         case REQ_STATUS_MERGE:
3662                 if (!status || status->status != 'U') {
3663                         report("Merging only possible for files with unmerged status ('U').");
3664                         return REQ_NONE;
3665                 }
3666                 open_mergetool(status->new.name);
3667                 break;
3668
3669         case REQ_EDIT:
3670                 if (!status)
3671                         return request;
3672
3673                 open_editor(status->status != '?', status->new.name);
3674                 break;
3675
3676         case REQ_ENTER:
3677                 /* After returning the status view has been split to
3678                  * show the stage view. No further reloading is
3679                  * necessary. */
3680                 status_enter(view, line);
3681                 return REQ_NONE;
3682
3683         case REQ_REFRESH:
3684                 /* Simply reload the view. */
3685                 break;
3686
3687         default:
3688                 return request;
3689         }
3690
3691         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
3692
3693         return REQ_NONE;
3694 }
3695
3696 static void
3697 status_select(struct view *view, struct line *line)
3698 {
3699         struct status *status = line->data;
3700         char file[SIZEOF_STR] = "all files";
3701         char *text;
3702         char *key;
3703
3704         if (status && !string_format(file, "'%s'", status->new.name))
3705                 return;
3706
3707         if (!status && line[1].type == LINE_STAT_NONE)
3708                 line++;
3709
3710         switch (line->type) {
3711         case LINE_STAT_STAGED:
3712                 text = "Press %s to unstage %s for commit";
3713                 break;
3714
3715         case LINE_STAT_UNSTAGED:
3716                 text = "Press %s to stage %s for commit";
3717                 break;
3718
3719         case LINE_STAT_UNTRACKED:
3720                 text = "Press %s to stage %s for addition";
3721                 break;
3722
3723         case LINE_STAT_NONE:
3724                 text = "Nothing to update";
3725                 break;
3726
3727         default:
3728                 die("line type %d not handled in switch", line->type);
3729         }
3730
3731         if (status && status->status == 'U') {
3732                 text = "Press %s to resolve conflict in %s";
3733                 key = get_key(REQ_STATUS_MERGE);
3734
3735         } else {
3736                 key = get_key(REQ_STATUS_UPDATE);
3737         }
3738
3739         string_format(view->ref, text, key, file);
3740 }
3741
3742 static bool
3743 status_grep(struct view *view, struct line *line)
3744 {
3745         struct status *status = line->data;
3746         enum { S_STATUS, S_NAME, S_END } state;
3747         char buf[2] = "?";
3748         regmatch_t pmatch;
3749
3750         if (!status)
3751                 return FALSE;
3752
3753         for (state = S_STATUS; state < S_END; state++) {
3754                 char *text;
3755
3756                 switch (state) {
3757                 case S_NAME:    text = status->new.name;        break;
3758                 case S_STATUS:
3759                         buf[0] = status->status;
3760                         text = buf;
3761                         break;
3762
3763                 default:
3764                         return FALSE;
3765                 }
3766
3767                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
3768                         return TRUE;
3769         }
3770
3771         return FALSE;
3772 }
3773
3774 static struct view_ops status_ops = {
3775         "file",
3776         status_open,
3777         NULL,
3778         status_draw,
3779         status_request,
3780         status_grep,
3781         status_select,
3782 };
3783
3784
3785 static bool
3786 stage_diff_line(FILE *pipe, struct line *line)
3787 {
3788         char *buf = line->data;
3789         size_t bufsize = strlen(buf);
3790         size_t written = 0;
3791
3792         while (!ferror(pipe) && written < bufsize) {
3793                 written += fwrite(buf + written, 1, bufsize - written, pipe);
3794         }
3795
3796         fputc('\n', pipe);
3797
3798         return written == bufsize;
3799 }
3800
3801 static struct line *
3802 stage_diff_hdr(struct view *view, struct line *line)
3803 {
3804         int diff_hdr_dir = line->type == LINE_DIFF_CHUNK ? -1 : 1;
3805         struct line *diff_hdr;
3806
3807         if (line->type == LINE_DIFF_CHUNK)
3808                 diff_hdr = line - 1;
3809         else
3810                 diff_hdr = view->line + 1;
3811
3812         while (diff_hdr > view->line && diff_hdr < view->line + view->lines) {
3813                 if (diff_hdr->type == LINE_DIFF_HEADER)
3814                         return diff_hdr;
3815
3816                 diff_hdr += diff_hdr_dir;
3817         }
3818
3819         return NULL;
3820 }
3821
3822 static bool
3823 stage_update_chunk(struct view *view, struct line *line)
3824 {
3825         char cmd[SIZEOF_STR];
3826         size_t cmdsize = 0;
3827         struct line *diff_hdr, *diff_chunk, *diff_end;
3828         FILE *pipe;
3829
3830         diff_hdr = stage_diff_hdr(view, line);
3831         if (!diff_hdr)
3832                 return FALSE;
3833
3834         if (opt_cdup[0] &&
3835             !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
3836                 return FALSE;
3837
3838         if (!string_format_from(cmd, &cmdsize,
3839                                 "git apply --cached %s - && "
3840                                 "git update-index -q --unmerged --refresh 2>/dev/null",
3841                                 stage_line_type == LINE_STAT_STAGED ? "-R" : ""))
3842                 return FALSE;
3843
3844         pipe = popen(cmd, "w");
3845         if (!pipe)
3846                 return FALSE;
3847
3848         diff_end = view->line + view->lines;
3849         if (line->type != LINE_DIFF_CHUNK) {
3850                 diff_chunk = diff_hdr;
3851
3852         } else {
3853                 for (diff_chunk = line + 1; diff_chunk < diff_end; diff_chunk++)
3854                         if (diff_chunk->type == LINE_DIFF_CHUNK ||
3855                             diff_chunk->type == LINE_DIFF_HEADER)
3856                                 diff_end = diff_chunk;
3857
3858                 diff_chunk = line;
3859
3860                 while (diff_hdr->type != LINE_DIFF_CHUNK) {
3861                         switch (diff_hdr->type) {
3862                         case LINE_DIFF_HEADER:
3863                         case LINE_DIFF_INDEX:
3864                         case LINE_DIFF_ADD:
3865                         case LINE_DIFF_DEL:
3866                                 break;
3867
3868                         default:
3869                                 diff_hdr++;
3870                                 continue;
3871                         }
3872
3873                         if (!stage_diff_line(pipe, diff_hdr++)) {
3874                                 pclose(pipe);
3875                                 return FALSE;
3876                         }
3877                 }
3878         }
3879
3880         while (diff_chunk < diff_end && stage_diff_line(pipe, diff_chunk))
3881                 diff_chunk++;
3882
3883         pclose(pipe);
3884
3885         if (diff_chunk != diff_end)
3886                 return FALSE;
3887
3888         return TRUE;
3889 }
3890
3891 static void
3892 stage_update(struct view *view, struct line *line)
3893 {
3894         if (stage_line_type != LINE_STAT_UNTRACKED &&
3895             (line->type == LINE_DIFF_CHUNK || !stage_status.status)) {
3896                 if (!stage_update_chunk(view, line)) {
3897                         report("Failed to apply chunk");
3898                         return;
3899                 }
3900
3901         } else if (!status_update_file(view, &stage_status, stage_line_type)) {
3902                 report("Failed to update file");
3903                 return;
3904         }
3905
3906         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
3907
3908         view = VIEW(REQ_VIEW_STATUS);
3909         if (view_is_displayed(view))
3910                 status_enter(view, &view->line[view->lineno]);
3911 }
3912
3913 static enum request
3914 stage_request(struct view *view, enum request request, struct line *line)
3915 {
3916         switch (request) {
3917         case REQ_STATUS_UPDATE:
3918                 stage_update(view, line);
3919                 break;
3920
3921         case REQ_EDIT:
3922                 if (!stage_status.new.name[0])
3923                         return request;
3924
3925                 open_editor(stage_status.status != '?', stage_status.new.name);
3926                 break;
3927
3928         case REQ_ENTER:
3929                 pager_request(view, request, line);
3930                 break;
3931
3932         default:
3933                 return request;
3934         }
3935
3936         return REQ_NONE;
3937 }
3938
3939 static struct view_ops stage_ops = {
3940         "line",
3941         NULL,
3942         pager_read,
3943         pager_draw,
3944         stage_request,
3945         pager_grep,
3946         pager_select,
3947 };
3948
3949
3950 /*
3951  * Revision graph
3952  */
3953
3954 struct commit {
3955         char id[SIZEOF_REV];            /* SHA1 ID. */
3956         char title[128];                /* First line of the commit message. */
3957         char author[75];                /* Author of the commit. */
3958         struct tm time;                 /* Date from the author ident. */
3959         struct ref **refs;              /* Repository references. */
3960         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
3961         size_t graph_size;              /* The width of the graph array. */
3962 };
3963
3964 /* Size of rev graph with no  "padding" columns */
3965 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
3966
3967 struct rev_graph {
3968         struct rev_graph *prev, *next, *parents;
3969         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
3970         size_t size;
3971         struct commit *commit;
3972         size_t pos;
3973         unsigned int boundary:1;
3974 };
3975
3976 /* Parents of the commit being visualized. */
3977 static struct rev_graph graph_parents[4];
3978
3979 /* The current stack of revisions on the graph. */
3980 static struct rev_graph graph_stacks[4] = {
3981         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
3982         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
3983         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
3984         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
3985 };
3986
3987 static inline bool
3988 graph_parent_is_merge(struct rev_graph *graph)
3989 {
3990         return graph->parents->size > 1;
3991 }
3992
3993 static inline void
3994 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
3995 {
3996         struct commit *commit = graph->commit;
3997
3998         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
3999                 commit->graph[commit->graph_size++] = symbol;
4000 }
4001
4002 static void
4003 done_rev_graph(struct rev_graph *graph)
4004 {
4005         if (graph_parent_is_merge(graph) &&
4006             graph->pos < graph->size - 1 &&
4007             graph->next->size == graph->size + graph->parents->size - 1) {
4008                 size_t i = graph->pos + graph->parents->size - 1;
4009
4010                 graph->commit->graph_size = i * 2;
4011                 while (i < graph->next->size - 1) {
4012                         append_to_rev_graph(graph, ' ');
4013                         append_to_rev_graph(graph, '\\');
4014                         i++;
4015                 }
4016         }
4017
4018         graph->size = graph->pos = 0;
4019         graph->commit = NULL;
4020         memset(graph->parents, 0, sizeof(*graph->parents));
4021 }
4022
4023 static void
4024 push_rev_graph(struct rev_graph *graph, char *parent)
4025 {
4026         int i;
4027
4028         /* "Collapse" duplicate parents lines.
4029          *
4030          * FIXME: This needs to also update update the drawn graph but
4031          * for now it just serves as a method for pruning graph lines. */
4032         for (i = 0; i < graph->size; i++)
4033                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
4034                         return;
4035
4036         if (graph->size < SIZEOF_REVITEMS) {
4037                 string_copy_rev(graph->rev[graph->size++], parent);
4038         }
4039 }
4040
4041 static chtype
4042 get_rev_graph_symbol(struct rev_graph *graph)
4043 {
4044         chtype symbol;
4045
4046         if (graph->boundary)
4047                 symbol = REVGRAPH_BOUND;
4048         else if (graph->parents->size == 0)
4049                 symbol = REVGRAPH_INIT;
4050         else if (graph_parent_is_merge(graph))
4051                 symbol = REVGRAPH_MERGE;
4052         else if (graph->pos >= graph->size)
4053                 symbol = REVGRAPH_BRANCH;
4054         else
4055                 symbol = REVGRAPH_COMMIT;
4056
4057         return symbol;
4058 }
4059
4060 static void
4061 draw_rev_graph(struct rev_graph *graph)
4062 {
4063         struct rev_filler {
4064                 chtype separator, line;
4065         };
4066         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
4067         static struct rev_filler fillers[] = {
4068                 { ' ',  REVGRAPH_LINE },
4069                 { '`',  '.' },
4070                 { '\'', ' ' },
4071                 { '/',  ' ' },
4072         };
4073         chtype symbol = get_rev_graph_symbol(graph);
4074         struct rev_filler *filler;
4075         size_t i;
4076
4077         filler = &fillers[DEFAULT];
4078
4079         for (i = 0; i < graph->pos; i++) {
4080                 append_to_rev_graph(graph, filler->line);
4081                 if (graph_parent_is_merge(graph->prev) &&
4082                     graph->prev->pos == i)
4083                         filler = &fillers[RSHARP];
4084
4085                 append_to_rev_graph(graph, filler->separator);
4086         }
4087
4088         /* Place the symbol for this revision. */
4089         append_to_rev_graph(graph, symbol);
4090
4091         if (graph->prev->size > graph->size)
4092                 filler = &fillers[RDIAG];
4093         else
4094                 filler = &fillers[DEFAULT];
4095
4096         i++;
4097
4098         for (; i < graph->size; i++) {
4099                 append_to_rev_graph(graph, filler->separator);
4100                 append_to_rev_graph(graph, filler->line);
4101                 if (graph_parent_is_merge(graph->prev) &&
4102                     i < graph->prev->pos + graph->parents->size)
4103                         filler = &fillers[RSHARP];
4104                 if (graph->prev->size > graph->size)
4105                         filler = &fillers[LDIAG];
4106         }
4107
4108         if (graph->prev->size > graph->size) {
4109                 append_to_rev_graph(graph, filler->separator);
4110                 if (filler->line != ' ')
4111                         append_to_rev_graph(graph, filler->line);
4112         }
4113 }
4114
4115 /* Prepare the next rev graph */
4116 static void
4117 prepare_rev_graph(struct rev_graph *graph)
4118 {
4119         size_t i;
4120
4121         /* First, traverse all lines of revisions up to the active one. */
4122         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
4123                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
4124                         break;
4125
4126                 push_rev_graph(graph->next, graph->rev[graph->pos]);
4127         }
4128
4129         /* Interleave the new revision parent(s). */
4130         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
4131                 push_rev_graph(graph->next, graph->parents->rev[i]);
4132
4133         /* Lastly, put any remaining revisions. */
4134         for (i = graph->pos + 1; i < graph->size; i++)
4135                 push_rev_graph(graph->next, graph->rev[i]);
4136 }
4137
4138 static void
4139 update_rev_graph(struct rev_graph *graph)
4140 {
4141         /* If this is the finalizing update ... */
4142         if (graph->commit)
4143                 prepare_rev_graph(graph);
4144
4145         /* Graph visualization needs a one rev look-ahead,
4146          * so the first update doesn't visualize anything. */
4147         if (!graph->prev->commit)
4148                 return;
4149
4150         draw_rev_graph(graph->prev);
4151         done_rev_graph(graph->prev->prev);
4152 }
4153
4154
4155 /*
4156  * Main view backend
4157  */
4158
4159 static bool
4160 main_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
4161 {
4162         char buf[DATE_COLS + 1];
4163         struct commit *commit = line->data;
4164         enum line_type type;
4165         int col = 0;
4166         size_t timelen;
4167         int tilde_attr;
4168         int space;
4169
4170         if (!*commit->author)
4171                 return FALSE;
4172
4173         space = view->width;
4174         wmove(view->win, lineno, col);
4175
4176         if (selected) {
4177                 type = LINE_CURSOR;
4178                 wattrset(view->win, get_line_attr(type));
4179                 wchgat(view->win, -1, 0, type, NULL);
4180                 tilde_attr = -1;
4181         } else {
4182                 type = LINE_MAIN_COMMIT;
4183                 wattrset(view->win, get_line_attr(LINE_MAIN_DATE));
4184                 tilde_attr = get_line_attr(LINE_MAIN_DELIM);
4185         }
4186
4187         {
4188                 int n;
4189
4190                 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time);
4191                 n = draw_text(
4192                         view, buf, view->width - col, col, FALSE, tilde_attr);
4193                 draw_text(
4194                         view, " ", view->width - col - n, col + n, FALSE,
4195                         tilde_attr);
4196
4197                 col += DATE_COLS;
4198                 wmove(view->win, lineno, col);
4199                 if (col >= view->width)
4200                         return TRUE;
4201         }
4202         if (type != LINE_CURSOR)
4203                 wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR));
4204
4205         {
4206                 int max_len;
4207
4208                 max_len = view->width - col;
4209                 if (max_len > AUTHOR_COLS - 1)
4210                         max_len = AUTHOR_COLS - 1;
4211                 draw_text(
4212                         view, commit->author, max_len, col, TRUE, tilde_attr);
4213                 col += AUTHOR_COLS;
4214                 if (col >= view->width)
4215                         return TRUE;
4216         }
4217
4218         if (opt_rev_graph && commit->graph_size) {
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                 /* Using waddch() instead of waddnstr() ensures that
4225                  * they'll be rendered correctly for the cursor line. */
4226                 for (i = 0; i < commit->graph_size; i++)
4227                         waddch(view->win, commit->graph[i]);
4228
4229                 waddch(view->win, ' ');
4230                 col += commit->graph_size + 1;
4231                 if (col >= view->width)
4232                         return TRUE;
4233         }
4234         if (type != LINE_CURSOR)
4235                 wattrset(view->win, A_NORMAL);
4236
4237         wmove(view->win, lineno, col);
4238
4239         if (commit->refs) {
4240                 size_t i = 0;
4241
4242                 do {
4243                         if (type == LINE_CURSOR)
4244                                 ;
4245                         else if (commit->refs[i]->tag)
4246                                 wattrset(view->win, get_line_attr(LINE_MAIN_TAG));
4247                         else if (commit->refs[i]->remote)
4248                                 wattrset(view->win, get_line_attr(LINE_MAIN_REMOTE));
4249                         else
4250                                 wattrset(view->win, get_line_attr(LINE_MAIN_REF));
4251
4252                         col += draw_text(
4253                                 view, "[", view->width - col, col, TRUE,
4254                                 tilde_attr);
4255                         col += draw_text(
4256                                 view, commit->refs[i]->name, view->width - col,
4257                                 col, TRUE, tilde_attr);
4258                         col += draw_text(
4259                                 view, "]", view->width - col, col, TRUE,
4260                                 tilde_attr);
4261                         if (type != LINE_CURSOR)
4262                                 wattrset(view->win, A_NORMAL);
4263                         col += draw_text(
4264                                 view, " ", view->width - col, col, TRUE,
4265                                 tilde_attr);
4266                         if (col >= view->width)
4267                                 return TRUE;
4268                 } while (commit->refs[i++]->next);
4269         }
4270
4271         if (type != LINE_CURSOR)
4272                 wattrset(view->win, get_line_attr(type));
4273
4274         col += draw_text(
4275                 view, commit->title, view->width - col, col, TRUE, tilde_attr);
4276
4277         return TRUE;
4278 }
4279
4280 /* Reads git log --pretty=raw output and parses it into the commit struct. */
4281 static bool
4282 main_read(struct view *view, char *line)
4283 {
4284         static struct rev_graph *graph = graph_stacks;
4285         enum line_type type;
4286         struct commit *commit;
4287
4288         if (!line) {
4289                 update_rev_graph(graph);
4290                 return TRUE;
4291         }
4292
4293         type = get_line_type(line);
4294         if (type == LINE_COMMIT) {
4295                 commit = calloc(1, sizeof(struct commit));
4296                 if (!commit)
4297                         return FALSE;
4298
4299                 line += STRING_SIZE("commit ");
4300                 if (*line == '-') {
4301                         graph->boundary = 1;
4302                         line++;
4303                 }
4304
4305                 string_copy_rev(commit->id, line);
4306                 commit->refs = get_refs(commit->id);
4307                 graph->commit = commit;
4308                 add_line_data(view, commit, LINE_MAIN_COMMIT);
4309                 return TRUE;
4310         }
4311
4312         if (!view->lines)
4313                 return TRUE;
4314         commit = view->line[view->lines - 1].data;
4315
4316         switch (type) {
4317         case LINE_PARENT:
4318                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
4319                 break;
4320
4321         case LINE_AUTHOR:
4322         {
4323                 /* Parse author lines where the name may be empty:
4324                  *      author  <email@address.tld> 1138474660 +0100
4325                  */
4326                 char *ident = line + STRING_SIZE("author ");
4327                 char *nameend = strchr(ident, '<');
4328                 char *emailend = strchr(ident, '>');
4329
4330                 if (!nameend || !emailend)
4331                         break;
4332
4333                 update_rev_graph(graph);
4334                 graph = graph->next;
4335
4336                 *nameend = *emailend = 0;
4337                 ident = chomp_string(ident);
4338                 if (!*ident) {
4339                         ident = chomp_string(nameend + 1);
4340                         if (!*ident)
4341                                 ident = "Unknown";
4342                 }
4343
4344                 string_ncopy(commit->author, ident, strlen(ident));
4345
4346                 /* Parse epoch and timezone */
4347                 if (emailend[1] == ' ') {
4348                         char *secs = emailend + 2;
4349                         char *zone = strchr(secs, ' ');
4350                         time_t time = (time_t) atol(secs);
4351
4352                         if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
4353                                 long tz;
4354
4355                                 zone++;
4356                                 tz  = ('0' - zone[1]) * 60 * 60 * 10;
4357                                 tz += ('0' - zone[2]) * 60 * 60;
4358                                 tz += ('0' - zone[3]) * 60;
4359                                 tz += ('0' - zone[4]) * 60;
4360
4361                                 if (zone[0] == '-')
4362                                         tz = -tz;
4363
4364                                 time -= tz;
4365                         }
4366
4367                         gmtime_r(&time, &commit->time);
4368                 }
4369                 break;
4370         }
4371         default:
4372                 /* Fill in the commit title if it has not already been set. */
4373                 if (commit->title[0])
4374                         break;
4375
4376                 /* Require titles to start with a non-space character at the
4377                  * offset used by git log. */
4378                 if (strncmp(line, "    ", 4))
4379                         break;
4380                 line += 4;
4381                 /* Well, if the title starts with a whitespace character,
4382                  * try to be forgiving.  Otherwise we end up with no title. */
4383                 while (isspace(*line))
4384                         line++;
4385                 if (*line == '\0')
4386                         break;
4387                 /* FIXME: More graceful handling of titles; append "..." to
4388                  * shortened titles, etc. */
4389
4390                 string_ncopy(commit->title, line, strlen(line));
4391         }
4392
4393         return TRUE;
4394 }
4395
4396 static enum request
4397 main_request(struct view *view, enum request request, struct line *line)
4398 {
4399         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4400
4401         if (request == REQ_ENTER)
4402                 open_view(view, REQ_VIEW_DIFF, flags);
4403         else
4404                 return request;
4405
4406         return REQ_NONE;
4407 }
4408
4409 static bool
4410 main_grep(struct view *view, struct line *line)
4411 {
4412         struct commit *commit = line->data;
4413         enum { S_TITLE, S_AUTHOR, S_DATE, S_END } state;
4414         char buf[DATE_COLS + 1];
4415         regmatch_t pmatch;
4416
4417         for (state = S_TITLE; state < S_END; state++) {
4418                 char *text;
4419
4420                 switch (state) {
4421                 case S_TITLE:   text = commit->title;   break;
4422                 case S_AUTHOR:  text = commit->author;  break;
4423                 case S_DATE:
4424                         if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
4425                                 continue;
4426                         text = buf;
4427                         break;
4428
4429                 default:
4430                         return FALSE;
4431                 }
4432
4433                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4434                         return TRUE;
4435         }
4436
4437         return FALSE;
4438 }
4439
4440 static void
4441 main_select(struct view *view, struct line *line)
4442 {
4443         struct commit *commit = line->data;
4444
4445         string_copy_rev(view->ref, commit->id);
4446         string_copy_rev(ref_commit, view->ref);
4447 }
4448
4449 static struct view_ops main_ops = {
4450         "commit",
4451         NULL,
4452         main_read,
4453         main_draw,
4454         main_request,
4455         main_grep,
4456         main_select,
4457 };
4458
4459
4460 /*
4461  * Unicode / UTF-8 handling
4462  *
4463  * NOTE: Much of the following code for dealing with unicode is derived from
4464  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
4465  * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
4466  */
4467
4468 /* I've (over)annotated a lot of code snippets because I am not entirely
4469  * confident that the approach taken by this small UTF-8 interface is correct.
4470  * --jonas */
4471
4472 static inline int
4473 unicode_width(unsigned long c)
4474 {
4475         if (c >= 0x1100 &&
4476            (c <= 0x115f                         /* Hangul Jamo */
4477             || c == 0x2329
4478             || c == 0x232a
4479             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
4480                                                 /* CJK ... Yi */
4481             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
4482             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
4483             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
4484             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
4485             || (c >= 0xffe0  && c <= 0xffe6)
4486             || (c >= 0x20000 && c <= 0x2fffd)
4487             || (c >= 0x30000 && c <= 0x3fffd)))
4488                 return 2;
4489
4490         return 1;
4491 }
4492
4493 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
4494  * Illegal bytes are set one. */
4495 static const unsigned char utf8_bytes[256] = {
4496         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,
4497         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,
4498         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,
4499         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,
4500         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,
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         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,
4503         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,
4504 };
4505
4506 /* Decode UTF-8 multi-byte representation into a unicode character. */
4507 static inline unsigned long
4508 utf8_to_unicode(const char *string, size_t length)
4509 {
4510         unsigned long unicode;
4511
4512         switch (length) {
4513         case 1:
4514                 unicode  =   string[0];
4515                 break;
4516         case 2:
4517                 unicode  =  (string[0] & 0x1f) << 6;
4518                 unicode +=  (string[1] & 0x3f);
4519                 break;
4520         case 3:
4521                 unicode  =  (string[0] & 0x0f) << 12;
4522                 unicode += ((string[1] & 0x3f) << 6);
4523                 unicode +=  (string[2] & 0x3f);
4524                 break;
4525         case 4:
4526                 unicode  =  (string[0] & 0x0f) << 18;
4527                 unicode += ((string[1] & 0x3f) << 12);
4528                 unicode += ((string[2] & 0x3f) << 6);
4529                 unicode +=  (string[3] & 0x3f);
4530                 break;
4531         case 5:
4532                 unicode  =  (string[0] & 0x0f) << 24;
4533                 unicode += ((string[1] & 0x3f) << 18);
4534                 unicode += ((string[2] & 0x3f) << 12);
4535                 unicode += ((string[3] & 0x3f) << 6);
4536                 unicode +=  (string[4] & 0x3f);
4537                 break;
4538         case 6:
4539                 unicode  =  (string[0] & 0x01) << 30;
4540                 unicode += ((string[1] & 0x3f) << 24);
4541                 unicode += ((string[2] & 0x3f) << 18);
4542                 unicode += ((string[3] & 0x3f) << 12);
4543                 unicode += ((string[4] & 0x3f) << 6);
4544                 unicode +=  (string[5] & 0x3f);
4545                 break;
4546         default:
4547                 die("Invalid unicode length");
4548         }
4549
4550         /* Invalid characters could return the special 0xfffd value but NUL
4551          * should be just as good. */
4552         return unicode > 0xffff ? 0 : unicode;
4553 }
4554
4555 /* Calculates how much of string can be shown within the given maximum width
4556  * and sets trimmed parameter to non-zero value if all of string could not be
4557  * shown.
4558  *
4559  * Additionally, adds to coloffset how many many columns to move to align with
4560  * the expected position. Takes into account how multi-byte and double-width
4561  * characters will effect the cursor position.
4562  *
4563  * Returns the number of bytes to output from string to satisfy max_width. */
4564 static size_t
4565 utf8_length(const char *string, size_t max_width, int *coloffset, int *trimmed)
4566 {
4567         const char *start = string;
4568         const char *end = strchr(string, '\0');
4569         size_t mbwidth = 0;
4570         size_t width = 0;
4571
4572         *trimmed = 0;
4573
4574         while (string < end) {
4575                 int c = *(unsigned char *) string;
4576                 unsigned char bytes = utf8_bytes[c];
4577                 size_t ucwidth;
4578                 unsigned long unicode;
4579
4580                 if (string + bytes > end)
4581                         break;
4582
4583                 /* Change representation to figure out whether
4584                  * it is a single- or double-width character. */
4585
4586                 unicode = utf8_to_unicode(string, bytes);
4587                 /* FIXME: Graceful handling of invalid unicode character. */
4588                 if (!unicode)
4589                         break;
4590
4591                 ucwidth = unicode_width(unicode);
4592                 width  += ucwidth;
4593                 if (width > max_width) {
4594                         *trimmed = 1;
4595                         break;
4596                 }
4597
4598                 /* The column offset collects the differences between the
4599                  * number of bytes encoding a character and the number of
4600                  * columns will be used for rendering said character.
4601                  *
4602                  * So if some character A is encoded in 2 bytes, but will be
4603                  * represented on the screen using only 1 byte this will and up
4604                  * adding 1 to the multi-byte column offset.
4605                  *
4606                  * Assumes that no double-width character can be encoding in
4607                  * less than two bytes. */
4608                 if (bytes > ucwidth)
4609                         mbwidth += bytes - ucwidth;
4610
4611                 string  += bytes;
4612         }
4613
4614         *coloffset += mbwidth;
4615
4616         return string - start;
4617 }
4618
4619
4620 /*
4621  * Status management
4622  */
4623
4624 /* Whether or not the curses interface has been initialized. */
4625 static bool cursed = FALSE;
4626
4627 /* The status window is used for polling keystrokes. */
4628 static WINDOW *status_win;
4629
4630 static bool status_empty = TRUE;
4631
4632 /* Update status and title window. */
4633 static void
4634 report(const char *msg, ...)
4635 {
4636         struct view *view = display[current_view];
4637
4638         if (input_mode)
4639                 return;
4640
4641         if (!view) {
4642                 char buf[SIZEOF_STR];
4643                 va_list args;
4644
4645                 va_start(args, msg);
4646                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
4647                         buf[sizeof(buf) - 1] = 0;
4648                         buf[sizeof(buf) - 2] = '.';
4649                         buf[sizeof(buf) - 3] = '.';
4650                         buf[sizeof(buf) - 4] = '.';
4651                 }
4652                 va_end(args);
4653                 die("%s", buf);
4654         }
4655
4656         if (!status_empty || *msg) {
4657                 va_list args;
4658
4659                 va_start(args, msg);
4660
4661                 wmove(status_win, 0, 0);
4662                 if (*msg) {
4663                         vwprintw(status_win, msg, args);
4664                         status_empty = FALSE;
4665                 } else {
4666                         status_empty = TRUE;
4667                 }
4668                 wclrtoeol(status_win);
4669                 wrefresh(status_win);
4670
4671                 va_end(args);
4672         }
4673
4674         update_view_title(view);
4675         update_display_cursor(view);
4676 }
4677
4678 /* Controls when nodelay should be in effect when polling user input. */
4679 static void
4680 set_nonblocking_input(bool loading)
4681 {
4682         static unsigned int loading_views;
4683
4684         if ((loading == FALSE && loading_views-- == 1) ||
4685             (loading == TRUE  && loading_views++ == 0))
4686                 nodelay(status_win, loading);
4687 }
4688
4689 static void
4690 init_display(void)
4691 {
4692         int x, y;
4693
4694         /* Initialize the curses library */
4695         if (isatty(STDIN_FILENO)) {
4696                 cursed = !!initscr();
4697         } else {
4698                 /* Leave stdin and stdout alone when acting as a pager. */
4699                 FILE *io = fopen("/dev/tty", "r+");
4700
4701                 if (!io)
4702                         die("Failed to open /dev/tty");
4703                 cursed = !!newterm(NULL, io, io);
4704         }
4705
4706         if (!cursed)
4707                 die("Failed to initialize curses");
4708
4709         nonl();         /* Tell curses not to do NL->CR/NL on output */
4710         cbreak();       /* Take input chars one at a time, no wait for \n */
4711         noecho();       /* Don't echo input */
4712         leaveok(stdscr, TRUE);
4713
4714         if (has_colors())
4715                 init_colors();
4716
4717         getmaxyx(stdscr, y, x);
4718         status_win = newwin(1, 0, y - 1, 0);
4719         if (!status_win)
4720                 die("Failed to create status window");
4721
4722         /* Enable keyboard mapping */
4723         keypad(status_win, TRUE);
4724         wbkgdset(status_win, get_line_attr(LINE_STATUS));
4725 }
4726
4727 static char *
4728 read_prompt(const char *prompt)
4729 {
4730         enum { READING, STOP, CANCEL } status = READING;
4731         static char buf[sizeof(opt_cmd) - STRING_SIZE("git \0")];
4732         int pos = 0;
4733
4734         while (status == READING) {
4735                 struct view *view;
4736                 int i, key;
4737
4738                 input_mode = TRUE;
4739
4740                 foreach_view (view, i)
4741                         update_view(view);
4742
4743                 input_mode = FALSE;
4744
4745                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
4746                 wclrtoeol(status_win);
4747
4748                 /* Refresh, accept single keystroke of input */
4749                 key = wgetch(status_win);
4750                 switch (key) {
4751                 case KEY_RETURN:
4752                 case KEY_ENTER:
4753                 case '\n':
4754                         status = pos ? STOP : CANCEL;
4755                         break;
4756
4757                 case KEY_BACKSPACE:
4758                         if (pos > 0)
4759                                 pos--;
4760                         else
4761                                 status = CANCEL;
4762                         break;
4763
4764                 case KEY_ESC:
4765                         status = CANCEL;
4766                         break;
4767
4768                 case ERR:
4769                         break;
4770
4771                 default:
4772                         if (pos >= sizeof(buf)) {
4773                                 report("Input string too long");
4774                                 return NULL;
4775                         }
4776
4777                         if (isprint(key))
4778                                 buf[pos++] = (char) key;
4779                 }
4780         }
4781
4782         /* Clear the status window */
4783         status_empty = FALSE;
4784         report("");
4785
4786         if (status == CANCEL)
4787                 return NULL;
4788
4789         buf[pos++] = 0;
4790
4791         return buf;
4792 }
4793
4794 /*
4795  * Repository references
4796  */
4797
4798 static struct ref *refs;
4799 static size_t refs_size;
4800
4801 /* Id <-> ref store */
4802 static struct ref ***id_refs;
4803 static size_t id_refs_size;
4804
4805 static struct ref **
4806 get_refs(char *id)
4807 {
4808         struct ref ***tmp_id_refs;
4809         struct ref **ref_list = NULL;
4810         size_t ref_list_size = 0;
4811         size_t i;
4812
4813         for (i = 0; i < id_refs_size; i++)
4814                 if (!strcmp(id, id_refs[i][0]->id))
4815                         return id_refs[i];
4816
4817         tmp_id_refs = realloc(id_refs, (id_refs_size + 1) * sizeof(*id_refs));
4818         if (!tmp_id_refs)
4819                 return NULL;
4820
4821         id_refs = tmp_id_refs;
4822
4823         for (i = 0; i < refs_size; i++) {
4824                 struct ref **tmp;
4825
4826                 if (strcmp(id, refs[i].id))
4827                         continue;
4828
4829                 tmp = realloc(ref_list, (ref_list_size + 1) * sizeof(*ref_list));
4830                 if (!tmp) {
4831                         if (ref_list)
4832                                 free(ref_list);
4833                         return NULL;
4834                 }
4835
4836                 ref_list = tmp;
4837                 if (ref_list_size > 0)
4838                         ref_list[ref_list_size - 1]->next = 1;
4839                 ref_list[ref_list_size] = &refs[i];
4840
4841                 /* XXX: The properties of the commit chains ensures that we can
4842                  * safely modify the shared ref. The repo references will
4843                  * always be similar for the same id. */
4844                 ref_list[ref_list_size]->next = 0;
4845                 ref_list_size++;
4846         }
4847
4848         if (ref_list)
4849                 id_refs[id_refs_size++] = ref_list;
4850
4851         return ref_list;
4852 }
4853
4854 static int
4855 read_ref(char *id, size_t idlen, char *name, size_t namelen)
4856 {
4857         struct ref *ref;
4858         bool tag = FALSE;
4859         bool remote = FALSE;
4860
4861         if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
4862                 /* Commits referenced by tags has "^{}" appended. */
4863                 if (name[namelen - 1] != '}')
4864                         return OK;
4865
4866                 while (namelen > 0 && name[namelen] != '^')
4867                         namelen--;
4868
4869                 tag = TRUE;
4870                 namelen -= STRING_SIZE("refs/tags/");
4871                 name    += STRING_SIZE("refs/tags/");
4872
4873         } else if (!strncmp(name, "refs/remotes/", STRING_SIZE("refs/remotes/"))) {
4874                 remote = TRUE;
4875                 namelen -= STRING_SIZE("refs/remotes/");
4876                 name    += STRING_SIZE("refs/remotes/");
4877
4878         } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
4879                 namelen -= STRING_SIZE("refs/heads/");
4880                 name    += STRING_SIZE("refs/heads/");
4881
4882         } else if (!strcmp(name, "HEAD")) {
4883                 return OK;
4884         }
4885
4886         refs = realloc(refs, sizeof(*refs) * (refs_size + 1));
4887         if (!refs)
4888                 return ERR;
4889
4890         ref = &refs[refs_size++];
4891         ref->name = malloc(namelen + 1);
4892         if (!ref->name)
4893                 return ERR;
4894
4895         strncpy(ref->name, name, namelen);
4896         ref->name[namelen] = 0;
4897         ref->tag = tag;
4898         ref->remote = remote;
4899         string_copy_rev(ref->id, id);
4900
4901         return OK;
4902 }
4903
4904 static int
4905 load_refs(void)
4906 {
4907         const char *cmd_env = getenv("TIG_LS_REMOTE");
4908         const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
4909
4910         return read_properties(popen(cmd, "r"), "\t", read_ref);
4911 }
4912
4913 static int
4914 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
4915 {
4916         if (!strcmp(name, "i18n.commitencoding"))
4917                 string_ncopy(opt_encoding, value, valuelen);
4918
4919         if (!strcmp(name, "core.editor"))
4920                 string_ncopy(opt_editor, value, valuelen);
4921
4922         return OK;
4923 }
4924
4925 static int
4926 load_repo_config(void)
4927 {
4928         return read_properties(popen(GIT_CONFIG " --list", "r"),
4929                                "=", read_repo_config_option);
4930 }
4931
4932 static int
4933 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
4934 {
4935         if (!opt_git_dir[0]) {
4936                 string_ncopy(opt_git_dir, name, namelen);
4937
4938         } else if (opt_is_inside_work_tree == -1) {
4939                 /* This can be 3 different values depending on the
4940                  * version of git being used. If git-rev-parse does not
4941                  * understand --is-inside-work-tree it will simply echo
4942                  * the option else either "true" or "false" is printed.
4943                  * Default to true for the unknown case. */
4944                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
4945
4946         } else {
4947                 string_ncopy(opt_cdup, name, namelen);
4948         }
4949
4950         return OK;
4951 }
4952
4953 /* XXX: The line outputted by "--show-cdup" can be empty so the option
4954  * must be the last one! */
4955 static int
4956 load_repo_info(void)
4957 {
4958         return read_properties(popen("git rev-parse --git-dir --is-inside-work-tree --show-cdup 2>/dev/null", "r"),
4959                                "=", read_repo_info);
4960 }
4961
4962 static int
4963 read_properties(FILE *pipe, const char *separators,
4964                 int (*read_property)(char *, size_t, char *, size_t))
4965 {
4966         char buffer[BUFSIZ];
4967         char *name;
4968         int state = OK;
4969
4970         if (!pipe)
4971                 return ERR;
4972
4973         while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
4974                 char *value;
4975                 size_t namelen;
4976                 size_t valuelen;
4977
4978                 name = chomp_string(name);
4979                 namelen = strcspn(name, separators);
4980
4981                 if (name[namelen]) {
4982                         name[namelen] = 0;
4983                         value = chomp_string(name + namelen + 1);
4984                         valuelen = strlen(value);
4985
4986                 } else {
4987                         value = "";
4988                         valuelen = 0;
4989                 }
4990
4991                 state = read_property(name, namelen, value, valuelen);
4992         }
4993
4994         if (state != ERR && ferror(pipe))
4995                 state = ERR;
4996
4997         pclose(pipe);
4998
4999         return state;
5000 }
5001
5002
5003 /*
5004  * Main
5005  */
5006
5007 static void __NORETURN
5008 quit(int sig)
5009 {
5010         /* XXX: Restore tty modes and let the OS cleanup the rest! */
5011         if (cursed)
5012                 endwin();
5013         exit(0);
5014 }
5015
5016 static void __NORETURN
5017 die(const char *err, ...)
5018 {
5019         va_list args;
5020
5021         endwin();
5022
5023         va_start(args, err);
5024         fputs("tig: ", stderr);
5025         vfprintf(stderr, err, args);
5026         fputs("\n", stderr);
5027         va_end(args);
5028
5029         exit(1);
5030 }
5031
5032 static void
5033 warn(const char *msg, ...)
5034 {
5035         va_list args;
5036
5037         va_start(args, msg);
5038         fputs("tig warning: ", stderr);
5039         vfprintf(stderr, msg, args);
5040         fputs("\n", stderr);
5041         va_end(args);
5042 }
5043
5044 int
5045 main(int argc, char *argv[])
5046 {
5047         struct view *view;
5048         enum request request;
5049         size_t i;
5050
5051         signal(SIGINT, quit);
5052
5053         if (setlocale(LC_ALL, "")) {
5054                 char *codeset = nl_langinfo(CODESET);
5055
5056                 string_ncopy(opt_codeset, codeset, strlen(codeset));
5057         }
5058
5059         if (load_repo_info() == ERR)
5060                 die("Failed to load repo info.");
5061
5062         if (load_options() == ERR)
5063                 die("Failed to load user config.");
5064
5065         /* Load the repo config file so options can be overwritten from
5066          * the command line. */
5067         if (load_repo_config() == ERR)
5068                 die("Failed to load repo config.");
5069
5070         if (!parse_options(argc, argv))
5071                 return 0;
5072
5073         /* Require a git repository unless when running in pager mode. */
5074         if (!opt_git_dir[0])
5075                 die("Not a git repository");
5076
5077         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
5078                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
5079                 if (opt_iconv == ICONV_NONE)
5080                         die("Failed to initialize character set conversion");
5081         }
5082
5083         if (load_refs() == ERR)
5084                 die("Failed to load refs.");
5085
5086         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
5087                 view->cmd_env = getenv(view->cmd_env);
5088
5089         request = opt_request;
5090
5091         init_display();
5092
5093         while (view_driver(display[current_view], request)) {
5094                 int key;
5095                 int i;
5096
5097                 foreach_view (view, i)
5098                         update_view(view);
5099
5100                 /* Refresh, accept single keystroke of input */
5101                 key = wgetch(status_win);
5102
5103                 /* wgetch() with nodelay() enabled returns ERR when there's no
5104                  * input. */
5105                 if (key == ERR) {
5106                         request = REQ_NONE;
5107                         continue;
5108                 }
5109
5110                 request = get_keybinding(display[current_view]->keymap, key);
5111
5112                 /* Some low-level request handling. This keeps access to
5113                  * status_win restricted. */
5114                 switch (request) {
5115                 case REQ_PROMPT:
5116                 {
5117                         char *cmd = read_prompt(":");
5118
5119                         if (cmd && string_format(opt_cmd, "git %s", cmd)) {
5120                                 if (strncmp(cmd, "show", 4) && isspace(cmd[4])) {
5121                                         opt_request = REQ_VIEW_DIFF;
5122                                 } else {
5123                                         opt_request = REQ_VIEW_PAGER;
5124                                 }
5125                                 break;
5126                         }
5127
5128                         request = REQ_NONE;
5129                         break;
5130                 }
5131                 case REQ_SEARCH:
5132                 case REQ_SEARCH_BACK:
5133                 {
5134                         const char *prompt = request == REQ_SEARCH
5135                                            ? "/" : "?";
5136                         char *search = read_prompt(prompt);
5137
5138                         if (search)
5139                                 string_ncopy(opt_search, search, strlen(search));
5140                         else
5141                                 request = REQ_NONE;
5142                         break;
5143                 }
5144                 case REQ_SCREEN_RESIZE:
5145                 {
5146                         int height, width;
5147
5148                         getmaxyx(stdscr, height, width);
5149
5150                         /* Resize the status view and let the view driver take
5151                          * care of resizing the displayed views. */
5152                         wresize(status_win, 1, width);
5153                         mvwin(status_win, height - 1, 0);
5154                         wrefresh(status_win);
5155                         break;
5156                 }
5157                 default:
5158                         break;
5159                 }
5160         }
5161
5162         quit(0);
5163
5164         return 0;
5165 }