draw_text: reduce indentation level
[tig] / tig.c
1 /* Copyright (c) 2006-2007 Jonas Fonseca <fonseca@diku.dk>
2  *
3  * This program is free software; you can redistribute it and/or
4  * modify it under the terms of the GNU General Public License as
5  * published by the Free Software Foundation; either version 2 of
6  * the License, or (at your option) any later version.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11  * GNU General Public License for more details.
12  */
13
14 #ifdef HAVE_CONFIG_H
15 #include "config.h"
16 #endif
17
18 #ifndef TIG_VERSION
19 #define TIG_VERSION "unknown-version"
20 #endif
21
22 #ifndef DEBUG
23 #define NDEBUG
24 #endif
25
26 #include <assert.h>
27 #include <errno.h>
28 #include <ctype.h>
29 #include <signal.h>
30 #include <stdarg.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <sys/types.h>
35 #include <sys/stat.h>
36 #include <unistd.h>
37 #include <time.h>
38
39 #include <regex.h>
40
41 #include <locale.h>
42 #include <langinfo.h>
43 #include <iconv.h>
44
45 /* ncurses(3): Must be defined to have extended wide-character functions. */
46 #define _XOPEN_SOURCE_EXTENDED
47
48 #include <curses.h>
49
50 #if __GNUC__ >= 3
51 #define __NORETURN __attribute__((__noreturn__))
52 #else
53 #define __NORETURN
54 #endif
55
56 static void __NORETURN die(const char *err, ...);
57 static void warn(const char *msg, ...);
58 static void report(const char *msg, ...);
59 static int read_properties(FILE *pipe, const char *separators, int (*read)(char *, size_t, char *, size_t));
60 static void set_nonblocking_input(bool loading);
61 static size_t utf8_length(const char *string, size_t max_width, int *trimmed, bool reserve);
62
63 #define ABS(x)          ((x) >= 0  ? (x) : -(x))
64 #define MIN(x, y)       ((x) < (y) ? (x) :  (y))
65
66 #define ARRAY_SIZE(x)   (sizeof(x) / sizeof(x[0]))
67 #define STRING_SIZE(x)  (sizeof(x) - 1)
68
69 #define SIZEOF_STR      1024    /* Default string size. */
70 #define SIZEOF_REF      256     /* Size of symbolic or SHA1 ID. */
71 #define SIZEOF_REV      41      /* Holds a SHA-1 and an ending NUL */
72
73 /* Revision graph */
74
75 #define REVGRAPH_INIT   'I'
76 #define REVGRAPH_MERGE  'M'
77 #define REVGRAPH_BRANCH '+'
78 #define REVGRAPH_COMMIT '*'
79 #define REVGRAPH_BOUND  '^'
80 #define REVGRAPH_LINE   '|'
81
82 #define SIZEOF_REVGRAPH 19      /* Size of revision ancestry graphics. */
83
84 /* This color name can be used to refer to the default term colors. */
85 #define COLOR_DEFAULT   (-1)
86
87 #define ICONV_NONE      ((iconv_t) -1)
88 #ifndef ICONV_CONST
89 #define ICONV_CONST     /* nothing */
90 #endif
91
92 /* The format and size of the date column in the main view. */
93 #define DATE_FORMAT     "%Y-%m-%d %H:%M"
94 #define DATE_COLS       STRING_SIZE("2006-04-29 14:21 ")
95
96 #define AUTHOR_COLS     20
97
98 /* The default interval between line numbers. */
99 #define NUMBER_INTERVAL 1
100
101 #define TABSIZE         8
102
103 #define SCALE_SPLIT_VIEW(height)        ((height) * 2 / 3)
104
105 #ifndef GIT_CONFIG
106 #define GIT_CONFIG "git config"
107 #endif
108
109 #define TIG_LS_REMOTE \
110         "git ls-remote $(git rev-parse --git-dir) 2>/dev/null"
111
112 #define TIG_DIFF_CMD \
113         "git show --pretty=fuller --no-color --root --patch-with-stat --find-copies-harder -C %s 2>/dev/null"
114
115 #define TIG_LOG_CMD     \
116         "git log --no-color --cc --stat -n100 %s 2>/dev/null"
117
118 #define TIG_MAIN_CMD \
119         "git log --no-color --topo-order --boundary --pretty=raw %s 2>/dev/null"
120
121 #define TIG_TREE_CMD    \
122         "git ls-tree %s %s"
123
124 #define TIG_BLOB_CMD    \
125         "git cat-file blob %s"
126
127 /* XXX: Needs to be defined to the empty string. */
128 #define TIG_HELP_CMD    ""
129 #define TIG_PAGER_CMD   ""
130 #define TIG_STATUS_CMD  ""
131 #define TIG_STAGE_CMD   ""
132
133 /* Some ascii-shorthands fitted into the ncurses namespace. */
134 #define KEY_TAB         '\t'
135 #define KEY_RETURN      '\r'
136 #define KEY_ESC         27
137
138
139 struct ref {
140         char *name;             /* Ref name; tag or head names are shortened. */
141         char id[SIZEOF_REV];    /* Commit SHA1 ID */
142         unsigned int tag:1;     /* Is it a tag? */
143         unsigned int remote:1;  /* Is it a remote ref? */
144         unsigned int next:1;    /* For ref lists: are there more refs? */
145 };
146
147 static struct ref **get_refs(char *id);
148
149 struct int_map {
150         const char *name;
151         int namelen;
152         int value;
153 };
154
155 static int
156 set_from_int_map(struct int_map *map, size_t map_size,
157                  int *value, const char *name, int namelen)
158 {
159
160         int i;
161
162         for (i = 0; i < map_size; i++)
163                 if (namelen == map[i].namelen &&
164                     !strncasecmp(name, map[i].name, namelen)) {
165                         *value = map[i].value;
166                         return OK;
167                 }
168
169         return ERR;
170 }
171
172
173 /*
174  * String helpers
175  */
176
177 static inline void
178 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
179 {
180         if (srclen > dstlen - 1)
181                 srclen = dstlen - 1;
182
183         strncpy(dst, src, srclen);
184         dst[srclen] = 0;
185 }
186
187 /* Shorthands for safely copying into a fixed buffer. */
188
189 #define string_copy(dst, src) \
190         string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
191
192 #define string_ncopy(dst, src, srclen) \
193         string_ncopy_do(dst, sizeof(dst), src, srclen)
194
195 #define string_copy_rev(dst, src) \
196         string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
197
198 #define string_add(dst, from, src) \
199         string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
200
201 static char *
202 chomp_string(char *name)
203 {
204         int namelen;
205
206         while (isspace(*name))
207                 name++;
208
209         namelen = strlen(name) - 1;
210         while (namelen > 0 && isspace(name[namelen]))
211                 name[namelen--] = 0;
212
213         return name;
214 }
215
216 static bool
217 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
218 {
219         va_list args;
220         size_t pos = bufpos ? *bufpos : 0;
221
222         va_start(args, fmt);
223         pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
224         va_end(args);
225
226         if (bufpos)
227                 *bufpos = pos;
228
229         return pos >= bufsize ? FALSE : TRUE;
230 }
231
232 #define string_format(buf, fmt, args...) \
233         string_nformat(buf, sizeof(buf), NULL, fmt, args)
234
235 #define string_format_from(buf, from, fmt, args...) \
236         string_nformat(buf, sizeof(buf), from, fmt, args)
237
238 static int
239 string_enum_compare(const char *str1, const char *str2, int len)
240 {
241         size_t i;
242
243 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
244
245         /* Diff-Header == DIFF_HEADER */
246         for (i = 0; i < len; i++) {
247                 if (toupper(str1[i]) == toupper(str2[i]))
248                         continue;
249
250                 if (string_enum_sep(str1[i]) &&
251                     string_enum_sep(str2[i]))
252                         continue;
253
254                 return str1[i] - str2[i];
255         }
256
257         return 0;
258 }
259
260 /* Shell quoting
261  *
262  * NOTE: The following is a slightly modified copy of the git project's shell
263  * quoting routines found in the quote.c file.
264  *
265  * Help to copy the thing properly quoted for the shell safety.  any single
266  * quote is replaced with '\'', any exclamation point is replaced with '\!',
267  * and the whole thing is enclosed in a
268  *
269  * E.g.
270  *  original     sq_quote     result
271  *  name     ==> name      ==> 'name'
272  *  a b      ==> a b       ==> 'a b'
273  *  a'b      ==> a'\''b    ==> 'a'\''b'
274  *  a!b      ==> a'\!'b    ==> 'a'\!'b'
275  */
276
277 static size_t
278 sq_quote(char buf[SIZEOF_STR], size_t bufsize, const char *src)
279 {
280         char c;
281
282 #define BUFPUT(x) do { if (bufsize < SIZEOF_STR) buf[bufsize++] = (x); } while (0)
283
284         BUFPUT('\'');
285         while ((c = *src++)) {
286                 if (c == '\'' || c == '!') {
287                         BUFPUT('\'');
288                         BUFPUT('\\');
289                         BUFPUT(c);
290                         BUFPUT('\'');
291                 } else {
292                         BUFPUT(c);
293                 }
294         }
295         BUFPUT('\'');
296
297         if (bufsize < SIZEOF_STR)
298                 buf[bufsize] = 0;
299
300         return bufsize;
301 }
302
303
304 /*
305  * User requests
306  */
307
308 #define REQ_INFO \
309         /* XXX: Keep the view request first and in sync with views[]. */ \
310         REQ_GROUP("View switching") \
311         REQ_(VIEW_MAIN,         "Show main view"), \
312         REQ_(VIEW_DIFF,         "Show diff view"), \
313         REQ_(VIEW_LOG,          "Show log view"), \
314         REQ_(VIEW_TREE,         "Show tree view"), \
315         REQ_(VIEW_BLOB,         "Show blob view"), \
316         REQ_(VIEW_HELP,         "Show help page"), \
317         REQ_(VIEW_PAGER,        "Show pager view"), \
318         REQ_(VIEW_STATUS,       "Show status view"), \
319         REQ_(VIEW_STAGE,        "Show stage view"), \
320         \
321         REQ_GROUP("View manipulation") \
322         REQ_(ENTER,             "Enter current line and scroll"), \
323         REQ_(NEXT,              "Move to next"), \
324         REQ_(PREVIOUS,          "Move to previous"), \
325         REQ_(VIEW_NEXT,         "Move focus to next view"), \
326         REQ_(REFRESH,           "Reload and refresh"), \
327         REQ_(VIEW_CLOSE,        "Close the current view"), \
328         REQ_(QUIT,              "Close all views and quit"), \
329         \
330         REQ_GROUP("Cursor navigation") \
331         REQ_(MOVE_UP,           "Move cursor one line up"), \
332         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
333         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
334         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
335         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
336         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
337         \
338         REQ_GROUP("Scrolling") \
339         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
340         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
341         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
342         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
343         \
344         REQ_GROUP("Searching") \
345         REQ_(SEARCH,            "Search the view"), \
346         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
347         REQ_(FIND_NEXT,         "Find next search match"), \
348         REQ_(FIND_PREV,         "Find previous search match"), \
349         \
350         REQ_GROUP("Misc") \
351         REQ_(PROMPT,            "Bring up the prompt"), \
352         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
353         REQ_(SCREEN_RESIZE,     "Resize the screen"), \
354         REQ_(SHOW_VERSION,      "Show version information"), \
355         REQ_(STOP_LOADING,      "Stop all loading views"), \
356         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
357         REQ_(TOGGLE_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 len = 0;
1464         int trimmed = FALSE;
1465
1466         if (max_len <= 0)
1467                 return 0;
1468
1469         if (opt_utf8) {
1470                 len = utf8_length(string, max_len, &trimmed, use_tilde);
1471         } else {
1472                 len = strlen(string);
1473                 if (len > max_len) {
1474                         if (use_tilde) {
1475                                 max_len -= 1;
1476                         }
1477                         len = max_len;
1478                         trimmed = TRUE;
1479                 }
1480         }
1481
1482         waddnstr(view->win, string, len);
1483         if (trimmed && use_tilde) {
1484                 if (tilde_attr != -1)
1485                         wattrset(view->win, tilde_attr);
1486                 waddch(view->win, '~');
1487                 len++;
1488         }
1489
1490         return len;
1491 }
1492
1493 static bool
1494 draw_view_line(struct view *view, unsigned int lineno)
1495 {
1496         struct line *line;
1497         bool selected = (view->offset + lineno == view->lineno);
1498         bool draw_ok;
1499
1500         assert(view_is_displayed(view));
1501
1502         if (view->offset + lineno >= view->lines)
1503                 return FALSE;
1504
1505         line = &view->line[view->offset + lineno];
1506
1507         if (selected) {
1508                 line->selected = TRUE;
1509                 view->ops->select(view, line);
1510         } else if (line->selected) {
1511                 line->selected = FALSE;
1512                 wmove(view->win, lineno, 0);
1513                 wclrtoeol(view->win);
1514         }
1515
1516         scrollok(view->win, FALSE);
1517         draw_ok = view->ops->draw(view, line, lineno, selected);
1518         scrollok(view->win, TRUE);
1519
1520         return draw_ok;
1521 }
1522
1523 static void
1524 redraw_view_from(struct view *view, int lineno)
1525 {
1526         assert(0 <= lineno && lineno < view->height);
1527
1528         for (; lineno < view->height; lineno++) {
1529                 if (!draw_view_line(view, lineno))
1530                         break;
1531         }
1532
1533         redrawwin(view->win);
1534         if (input_mode)
1535                 wnoutrefresh(view->win);
1536         else
1537                 wrefresh(view->win);
1538 }
1539
1540 static void
1541 redraw_view(struct view *view)
1542 {
1543         wclear(view->win);
1544         redraw_view_from(view, 0);
1545 }
1546
1547
1548 static void
1549 update_view_title(struct view *view)
1550 {
1551         char buf[SIZEOF_STR];
1552         char state[SIZEOF_STR];
1553         size_t bufpos = 0, statelen = 0;
1554
1555         assert(view_is_displayed(view));
1556
1557         if (view != VIEW(REQ_VIEW_STATUS) && (view->lines || view->pipe)) {
1558                 unsigned int view_lines = view->offset + view->height;
1559                 unsigned int lines = view->lines
1560                                    ? MIN(view_lines, view->lines) * 100 / view->lines
1561                                    : 0;
1562
1563                 string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
1564                                    view->ops->type,
1565                                    view->lineno + 1,
1566                                    view->lines,
1567                                    lines);
1568
1569                 if (view->pipe) {
1570                         time_t secs = time(NULL) - view->start_time;
1571
1572                         /* Three git seconds are a long time ... */
1573                         if (secs > 2)
1574                                 string_format_from(state, &statelen, " %lds", secs);
1575                 }
1576         }
1577
1578         string_format_from(buf, &bufpos, "[%s]", view->name);
1579         if (*view->ref && bufpos < view->width) {
1580                 size_t refsize = strlen(view->ref);
1581                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
1582
1583                 if (minsize < view->width)
1584                         refsize = view->width - minsize + 7;
1585                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
1586         }
1587
1588         if (statelen && bufpos < view->width) {
1589                 string_format_from(buf, &bufpos, " %s", state);
1590         }
1591
1592         if (view == display[current_view])
1593                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
1594         else
1595                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
1596
1597         mvwaddnstr(view->title, 0, 0, buf, bufpos);
1598         wclrtoeol(view->title);
1599         wmove(view->title, 0, view->width - 1);
1600
1601         if (input_mode)
1602                 wnoutrefresh(view->title);
1603         else
1604                 wrefresh(view->title);
1605 }
1606
1607 static void
1608 resize_display(void)
1609 {
1610         int offset, i;
1611         struct view *base = display[0];
1612         struct view *view = display[1] ? display[1] : display[0];
1613
1614         /* Setup window dimensions */
1615
1616         getmaxyx(stdscr, base->height, base->width);
1617
1618         /* Make room for the status window. */
1619         base->height -= 1;
1620
1621         if (view != base) {
1622                 /* Horizontal split. */
1623                 view->width   = base->width;
1624                 view->height  = SCALE_SPLIT_VIEW(base->height);
1625                 base->height -= view->height;
1626
1627                 /* Make room for the title bar. */
1628                 view->height -= 1;
1629         }
1630
1631         /* Make room for the title bar. */
1632         base->height -= 1;
1633
1634         offset = 0;
1635
1636         foreach_displayed_view (view, i) {
1637                 if (!view->win) {
1638                         view->win = newwin(view->height, 0, offset, 0);
1639                         if (!view->win)
1640                                 die("Failed to create %s view", view->name);
1641
1642                         scrollok(view->win, TRUE);
1643
1644                         view->title = newwin(1, 0, offset + view->height, 0);
1645                         if (!view->title)
1646                                 die("Failed to create title window");
1647
1648                 } else {
1649                         wresize(view->win, view->height, view->width);
1650                         mvwin(view->win,   offset, 0);
1651                         mvwin(view->title, offset + view->height, 0);
1652                 }
1653
1654                 offset += view->height + 1;
1655         }
1656 }
1657
1658 static void
1659 redraw_display(void)
1660 {
1661         struct view *view;
1662         int i;
1663
1664         foreach_displayed_view (view, i) {
1665                 redraw_view(view);
1666                 update_view_title(view);
1667         }
1668 }
1669
1670 static void
1671 update_display_cursor(struct view *view)
1672 {
1673         /* Move the cursor to the right-most column of the cursor line.
1674          *
1675          * XXX: This could turn out to be a bit expensive, but it ensures that
1676          * the cursor does not jump around. */
1677         if (view->lines) {
1678                 wmove(view->win, view->lineno - view->offset, view->width - 1);
1679                 wrefresh(view->win);
1680         }
1681 }
1682
1683 /*
1684  * Navigation
1685  */
1686
1687 /* Scrolling backend */
1688 static void
1689 do_scroll_view(struct view *view, int lines)
1690 {
1691         bool redraw_current_line = FALSE;
1692
1693         /* The rendering expects the new offset. */
1694         view->offset += lines;
1695
1696         assert(0 <= view->offset && view->offset < view->lines);
1697         assert(lines);
1698
1699         /* Move current line into the view. */
1700         if (view->lineno < view->offset) {
1701                 view->lineno = view->offset;
1702                 redraw_current_line = TRUE;
1703         } else if (view->lineno >= view->offset + view->height) {
1704                 view->lineno = view->offset + view->height - 1;
1705                 redraw_current_line = TRUE;
1706         }
1707
1708         assert(view->offset <= view->lineno && view->lineno < view->lines);
1709
1710         /* Redraw the whole screen if scrolling is pointless. */
1711         if (view->height < ABS(lines)) {
1712                 redraw_view(view);
1713
1714         } else {
1715                 int line = lines > 0 ? view->height - lines : 0;
1716                 int end = line + ABS(lines);
1717
1718                 wscrl(view->win, lines);
1719
1720                 for (; line < end; line++) {
1721                         if (!draw_view_line(view, line))
1722                                 break;
1723                 }
1724
1725                 if (redraw_current_line)
1726                         draw_view_line(view, view->lineno - view->offset);
1727         }
1728
1729         redrawwin(view->win);
1730         wrefresh(view->win);
1731         report("");
1732 }
1733
1734 /* Scroll frontend */
1735 static void
1736 scroll_view(struct view *view, enum request request)
1737 {
1738         int lines = 1;
1739
1740         assert(view_is_displayed(view));
1741
1742         switch (request) {
1743         case REQ_SCROLL_PAGE_DOWN:
1744                 lines = view->height;
1745         case REQ_SCROLL_LINE_DOWN:
1746                 if (view->offset + lines > view->lines)
1747                         lines = view->lines - view->offset;
1748
1749                 if (lines == 0 || view->offset + view->height >= view->lines) {
1750                         report("Cannot scroll beyond the last line");
1751                         return;
1752                 }
1753                 break;
1754
1755         case REQ_SCROLL_PAGE_UP:
1756                 lines = view->height;
1757         case REQ_SCROLL_LINE_UP:
1758                 if (lines > view->offset)
1759                         lines = view->offset;
1760
1761                 if (lines == 0) {
1762                         report("Cannot scroll beyond the first line");
1763                         return;
1764                 }
1765
1766                 lines = -lines;
1767                 break;
1768
1769         default:
1770                 die("request %d not handled in switch", request);
1771         }
1772
1773         do_scroll_view(view, lines);
1774 }
1775
1776 /* Cursor moving */
1777 static void
1778 move_view(struct view *view, enum request request)
1779 {
1780         int scroll_steps = 0;
1781         int steps;
1782
1783         switch (request) {
1784         case REQ_MOVE_FIRST_LINE:
1785                 steps = -view->lineno;
1786                 break;
1787
1788         case REQ_MOVE_LAST_LINE:
1789                 steps = view->lines - view->lineno - 1;
1790                 break;
1791
1792         case REQ_MOVE_PAGE_UP:
1793                 steps = view->height > view->lineno
1794                       ? -view->lineno : -view->height;
1795                 break;
1796
1797         case REQ_MOVE_PAGE_DOWN:
1798                 steps = view->lineno + view->height >= view->lines
1799                       ? view->lines - view->lineno - 1 : view->height;
1800                 break;
1801
1802         case REQ_MOVE_UP:
1803                 steps = -1;
1804                 break;
1805
1806         case REQ_MOVE_DOWN:
1807                 steps = 1;
1808                 break;
1809
1810         default:
1811                 die("request %d not handled in switch", request);
1812         }
1813
1814         if (steps <= 0 && view->lineno == 0) {
1815                 report("Cannot move beyond the first line");
1816                 return;
1817
1818         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
1819                 report("Cannot move beyond the last line");
1820                 return;
1821         }
1822
1823         /* Move the current line */
1824         view->lineno += steps;
1825         assert(0 <= view->lineno && view->lineno < view->lines);
1826
1827         /* Check whether the view needs to be scrolled */
1828         if (view->lineno < view->offset ||
1829             view->lineno >= view->offset + view->height) {
1830                 scroll_steps = steps;
1831                 if (steps < 0 && -steps > view->offset) {
1832                         scroll_steps = -view->offset;
1833
1834                 } else if (steps > 0) {
1835                         if (view->lineno == view->lines - 1 &&
1836                             view->lines > view->height) {
1837                                 scroll_steps = view->lines - view->offset - 1;
1838                                 if (scroll_steps >= view->height)
1839                                         scroll_steps -= view->height - 1;
1840                         }
1841                 }
1842         }
1843
1844         if (!view_is_displayed(view)) {
1845                 view->offset += scroll_steps;
1846                 assert(0 <= view->offset && view->offset < view->lines);
1847                 view->ops->select(view, &view->line[view->lineno]);
1848                 return;
1849         }
1850
1851         /* Repaint the old "current" line if we be scrolling */
1852         if (ABS(steps) < view->height)
1853                 draw_view_line(view, view->lineno - steps - view->offset);
1854
1855         if (scroll_steps) {
1856                 do_scroll_view(view, scroll_steps);
1857                 return;
1858         }
1859
1860         /* Draw the current line */
1861         draw_view_line(view, view->lineno - view->offset);
1862
1863         redrawwin(view->win);
1864         wrefresh(view->win);
1865         report("");
1866 }
1867
1868
1869 /*
1870  * Searching
1871  */
1872
1873 static void search_view(struct view *view, enum request request);
1874
1875 static bool
1876 find_next_line(struct view *view, unsigned long lineno, struct line *line)
1877 {
1878         assert(view_is_displayed(view));
1879
1880         if (!view->ops->grep(view, line))
1881                 return FALSE;
1882
1883         if (lineno - view->offset >= view->height) {
1884                 view->offset = lineno;
1885                 view->lineno = lineno;
1886                 redraw_view(view);
1887
1888         } else {
1889                 unsigned long old_lineno = view->lineno - view->offset;
1890
1891                 view->lineno = lineno;
1892                 draw_view_line(view, old_lineno);
1893
1894                 draw_view_line(view, view->lineno - view->offset);
1895                 redrawwin(view->win);
1896                 wrefresh(view->win);
1897         }
1898
1899         report("Line %ld matches '%s'", lineno + 1, view->grep);
1900         return TRUE;
1901 }
1902
1903 static void
1904 find_next(struct view *view, enum request request)
1905 {
1906         unsigned long lineno = view->lineno;
1907         int direction;
1908
1909         if (!*view->grep) {
1910                 if (!*opt_search)
1911                         report("No previous search");
1912                 else
1913                         search_view(view, request);
1914                 return;
1915         }
1916
1917         switch (request) {
1918         case REQ_SEARCH:
1919         case REQ_FIND_NEXT:
1920                 direction = 1;
1921                 break;
1922
1923         case REQ_SEARCH_BACK:
1924         case REQ_FIND_PREV:
1925                 direction = -1;
1926                 break;
1927
1928         default:
1929                 return;
1930         }
1931
1932         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
1933                 lineno += direction;
1934
1935         /* Note, lineno is unsigned long so will wrap around in which case it
1936          * will become bigger than view->lines. */
1937         for (; lineno < view->lines; lineno += direction) {
1938                 struct line *line = &view->line[lineno];
1939
1940                 if (find_next_line(view, lineno, line))
1941                         return;
1942         }
1943
1944         report("No match found for '%s'", view->grep);
1945 }
1946
1947 static void
1948 search_view(struct view *view, enum request request)
1949 {
1950         int regex_err;
1951
1952         if (view->regex) {
1953                 regfree(view->regex);
1954                 *view->grep = 0;
1955         } else {
1956                 view->regex = calloc(1, sizeof(*view->regex));
1957                 if (!view->regex)
1958                         return;
1959         }
1960
1961         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
1962         if (regex_err != 0) {
1963                 char buf[SIZEOF_STR] = "unknown error";
1964
1965                 regerror(regex_err, view->regex, buf, sizeof(buf));
1966                 report("Search failed: %s", buf);
1967                 return;
1968         }
1969
1970         string_copy(view->grep, opt_search);
1971
1972         find_next(view, request);
1973 }
1974
1975 /*
1976  * Incremental updating
1977  */
1978
1979 static void
1980 end_update(struct view *view)
1981 {
1982         if (!view->pipe)
1983                 return;
1984         set_nonblocking_input(FALSE);
1985         if (view->pipe == stdin)
1986                 fclose(view->pipe);
1987         else
1988                 pclose(view->pipe);
1989         view->pipe = NULL;
1990 }
1991
1992 static bool
1993 begin_update(struct view *view)
1994 {
1995         if (view->pipe)
1996                 end_update(view);
1997
1998         if (opt_cmd[0]) {
1999                 string_copy(view->cmd, opt_cmd);
2000                 opt_cmd[0] = 0;
2001                 /* When running random commands, initially show the
2002                  * command in the title. However, it maybe later be
2003                  * overwritten if a commit line is selected. */
2004                 if (view == VIEW(REQ_VIEW_PAGER))
2005                         string_copy(view->ref, view->cmd);
2006                 else
2007                         view->ref[0] = 0;
2008
2009         } else if (view == VIEW(REQ_VIEW_TREE)) {
2010                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
2011                 char path[SIZEOF_STR];
2012
2013                 if (strcmp(view->vid, view->id))
2014                         opt_path[0] = path[0] = 0;
2015                 else if (sq_quote(path, 0, opt_path) >= sizeof(path))
2016                         return FALSE;
2017
2018                 if (!string_format(view->cmd, format, view->id, path))
2019                         return FALSE;
2020
2021         } else {
2022                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
2023                 const char *id = view->id;
2024
2025                 if (!string_format(view->cmd, format, id, id, id, id, id))
2026                         return FALSE;
2027
2028                 /* Put the current ref_* value to the view title ref
2029                  * member. This is needed by the blob view. Most other
2030                  * views sets it automatically after loading because the
2031                  * first line is a commit line. */
2032                 string_copy_rev(view->ref, view->id);
2033         }
2034
2035         /* Special case for the pager view. */
2036         if (opt_pipe) {
2037                 view->pipe = opt_pipe;
2038                 opt_pipe = NULL;
2039         } else {
2040                 view->pipe = popen(view->cmd, "r");
2041         }
2042
2043         if (!view->pipe)
2044                 return FALSE;
2045
2046         set_nonblocking_input(TRUE);
2047
2048         view->offset = 0;
2049         view->lines  = 0;
2050         view->lineno = 0;
2051         string_copy_rev(view->vid, view->id);
2052
2053         if (view->line) {
2054                 int i;
2055
2056                 for (i = 0; i < view->lines; i++)
2057                         if (view->line[i].data)
2058                                 free(view->line[i].data);
2059
2060                 free(view->line);
2061                 view->line = NULL;
2062         }
2063
2064         view->start_time = time(NULL);
2065
2066         return TRUE;
2067 }
2068
2069 static struct line *
2070 realloc_lines(struct view *view, size_t line_size)
2071 {
2072         struct line *tmp = realloc(view->line, sizeof(*view->line) * line_size);
2073
2074         if (!tmp)
2075                 return NULL;
2076
2077         view->line = tmp;
2078         view->line_size = line_size;
2079         return view->line;
2080 }
2081
2082 static bool
2083 update_view(struct view *view)
2084 {
2085         char in_buffer[BUFSIZ];
2086         char out_buffer[BUFSIZ * 2];
2087         char *line;
2088         /* The number of lines to read. If too low it will cause too much
2089          * redrawing (and possible flickering), if too high responsiveness
2090          * will suffer. */
2091         unsigned long lines = view->height;
2092         int redraw_from = -1;
2093
2094         if (!view->pipe)
2095                 return TRUE;
2096
2097         /* Only redraw if lines are visible. */
2098         if (view->offset + view->height >= view->lines)
2099                 redraw_from = view->lines - view->offset;
2100
2101         /* FIXME: This is probably not perfect for backgrounded views. */
2102         if (!realloc_lines(view, view->lines + lines))
2103                 goto alloc_error;
2104
2105         while ((line = fgets(in_buffer, sizeof(in_buffer), view->pipe))) {
2106                 size_t linelen = strlen(line);
2107
2108                 if (linelen)
2109                         line[linelen - 1] = 0;
2110
2111                 if (opt_iconv != ICONV_NONE) {
2112                         ICONV_CONST char *inbuf = line;
2113                         size_t inlen = linelen;
2114
2115                         char *outbuf = out_buffer;
2116                         size_t outlen = sizeof(out_buffer);
2117
2118                         size_t ret;
2119
2120                         ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2121                         if (ret != (size_t) -1) {
2122                                 line = out_buffer;
2123                                 linelen = strlen(out_buffer);
2124                         }
2125                 }
2126
2127                 if (!view->ops->read(view, line))
2128                         goto alloc_error;
2129
2130                 if (lines-- == 1)
2131                         break;
2132         }
2133
2134         {
2135                 int digits;
2136
2137                 lines = view->lines;
2138                 for (digits = 0; lines; digits++)
2139                         lines /= 10;
2140
2141                 /* Keep the displayed view in sync with line number scaling. */
2142                 if (digits != view->digits) {
2143                         view->digits = digits;
2144                         redraw_from = 0;
2145                 }
2146         }
2147
2148         if (!view_is_displayed(view))
2149                 goto check_pipe;
2150
2151         if (view == VIEW(REQ_VIEW_TREE)) {
2152                 /* Clear the view and redraw everything since the tree sorting
2153                  * might have rearranged things. */
2154                 redraw_view(view);
2155
2156         } else if (redraw_from >= 0) {
2157                 /* If this is an incremental update, redraw the previous line
2158                  * since for commits some members could have changed when
2159                  * loading the main view. */
2160                 if (redraw_from > 0)
2161                         redraw_from--;
2162
2163                 /* Since revision graph visualization requires knowledge
2164                  * about the parent commit, it causes a further one-off
2165                  * needed to be redrawn for incremental updates. */
2166                 if (redraw_from > 0 && opt_rev_graph)
2167                         redraw_from--;
2168
2169                 /* Incrementally draw avoids flickering. */
2170                 redraw_view_from(view, redraw_from);
2171         }
2172
2173         /* Update the title _after_ the redraw so that if the redraw picks up a
2174          * commit reference in view->ref it'll be available here. */
2175         update_view_title(view);
2176
2177 check_pipe:
2178         if (ferror(view->pipe)) {
2179                 report("Failed to read: %s", strerror(errno));
2180                 goto end;
2181
2182         } else if (feof(view->pipe)) {
2183                 report("");
2184                 goto end;
2185         }
2186
2187         return TRUE;
2188
2189 alloc_error:
2190         report("Allocation failure");
2191
2192 end:
2193         view->ops->read(view, NULL);
2194         end_update(view);
2195         return FALSE;
2196 }
2197
2198 static struct line *
2199 add_line_data(struct view *view, void *data, enum line_type type)
2200 {
2201         struct line *line = &view->line[view->lines++];
2202
2203         memset(line, 0, sizeof(*line));
2204         line->type = type;
2205         line->data = data;
2206
2207         return line;
2208 }
2209
2210 static struct line *
2211 add_line_text(struct view *view, char *data, enum line_type type)
2212 {
2213         if (data)
2214                 data = strdup(data);
2215
2216         return data ? add_line_data(view, data, type) : NULL;
2217 }
2218
2219
2220 /*
2221  * View opening
2222  */
2223
2224 enum open_flags {
2225         OPEN_DEFAULT = 0,       /* Use default view switching. */
2226         OPEN_SPLIT = 1,         /* Split current view. */
2227         OPEN_BACKGROUNDED = 2,  /* Backgrounded. */
2228         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
2229 };
2230
2231 static void
2232 open_view(struct view *prev, enum request request, enum open_flags flags)
2233 {
2234         bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2235         bool split = !!(flags & OPEN_SPLIT);
2236         bool reload = !!(flags & OPEN_RELOAD);
2237         struct view *view = VIEW(request);
2238         int nviews = displayed_views();
2239         struct view *base_view = display[0];
2240
2241         if (view == prev && nviews == 1 && !reload) {
2242                 report("Already in %s view", view->name);
2243                 return;
2244         }
2245
2246         if (view->ops->open) {
2247                 if (!view->ops->open(view)) {
2248                         report("Failed to load %s view", view->name);
2249                         return;
2250                 }
2251
2252         } else if ((reload || strcmp(view->vid, view->id)) &&
2253                    !begin_update(view)) {
2254                 report("Failed to load %s view", view->name);
2255                 return;
2256         }
2257
2258         if (split) {
2259                 display[1] = view;
2260                 if (!backgrounded)
2261                         current_view = 1;
2262         } else {
2263                 /* Maximize the current view. */
2264                 memset(display, 0, sizeof(display));
2265                 current_view = 0;
2266                 display[current_view] = view;
2267         }
2268
2269         /* Resize the view when switching between split- and full-screen,
2270          * or when switching between two different full-screen views. */
2271         if (nviews != displayed_views() ||
2272             (nviews == 1 && base_view != display[0]))
2273                 resize_display();
2274
2275         if (split && prev->lineno - prev->offset >= prev->height) {
2276                 /* Take the title line into account. */
2277                 int lines = prev->lineno - prev->offset - prev->height + 1;
2278
2279                 /* Scroll the view that was split if the current line is
2280                  * outside the new limited view. */
2281                 do_scroll_view(prev, lines);
2282         }
2283
2284         if (prev && view != prev) {
2285                 if (split && !backgrounded) {
2286                         /* "Blur" the previous view. */
2287                         update_view_title(prev);
2288                 }
2289
2290                 view->parent = prev;
2291         }
2292
2293         if (view->pipe && view->lines == 0) {
2294                 /* Clear the old view and let the incremental updating refill
2295                  * the screen. */
2296                 wclear(view->win);
2297                 report("");
2298         } else {
2299                 redraw_view(view);
2300                 report("");
2301         }
2302
2303         /* If the view is backgrounded the above calls to report()
2304          * won't redraw the view title. */
2305         if (backgrounded)
2306                 update_view_title(view);
2307 }
2308
2309 static void
2310 open_external_viewer(const char *cmd)
2311 {
2312         def_prog_mode();           /* save current tty modes */
2313         endwin();                  /* restore original tty modes */
2314         system(cmd);
2315         fprintf(stderr, "Press Enter to continue");
2316         getc(stdin);
2317         reset_prog_mode();
2318         redraw_display();
2319 }
2320
2321 static void
2322 open_mergetool(const char *file)
2323 {
2324         char cmd[SIZEOF_STR];
2325         char file_sq[SIZEOF_STR];
2326
2327         if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2328             string_format(cmd, "git mergetool %s", file_sq)) {
2329                 open_external_viewer(cmd);
2330         }
2331 }
2332
2333 static void
2334 open_editor(bool from_root, const char *file)
2335 {
2336         char cmd[SIZEOF_STR];
2337         char file_sq[SIZEOF_STR];
2338         char *editor;
2339         char *prefix = from_root ? opt_cdup : "";
2340
2341         editor = getenv("GIT_EDITOR");
2342         if (!editor && *opt_editor)
2343                 editor = opt_editor;
2344         if (!editor)
2345                 editor = getenv("VISUAL");
2346         if (!editor)
2347                 editor = getenv("EDITOR");
2348         if (!editor)
2349                 editor = "vi";
2350
2351         if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2352             string_format(cmd, "%s %s%s", editor, prefix, file_sq)) {
2353                 open_external_viewer(cmd);
2354         }
2355 }
2356
2357 static void
2358 open_run_request(enum request request)
2359 {
2360         struct run_request *req = get_run_request(request);
2361         char buf[SIZEOF_STR * 2];
2362         size_t bufpos;
2363         char *cmd;
2364
2365         if (!req) {
2366                 report("Unknown run request");
2367                 return;
2368         }
2369
2370         bufpos = 0;
2371         cmd = req->cmd;
2372
2373         while (cmd) {
2374                 char *next = strstr(cmd, "%(");
2375                 int len = next - cmd;
2376                 char *value;
2377
2378                 if (!next) {
2379                         len = strlen(cmd);
2380                         value = "";
2381
2382                 } else if (!strncmp(next, "%(head)", 7)) {
2383                         value = ref_head;
2384
2385                 } else if (!strncmp(next, "%(commit)", 9)) {
2386                         value = ref_commit;
2387
2388                 } else if (!strncmp(next, "%(blob)", 7)) {
2389                         value = ref_blob;
2390
2391                 } else {
2392                         report("Unknown replacement in run request: `%s`", req->cmd);
2393                         return;
2394                 }
2395
2396                 if (!string_format_from(buf, &bufpos, "%.*s%s", len, cmd, value))
2397                         return;
2398
2399                 if (next)
2400                         next = strchr(next, ')') + 1;
2401                 cmd = next;
2402         }
2403
2404         open_external_viewer(buf);
2405 }
2406
2407 /*
2408  * User request switch noodle
2409  */
2410
2411 static int
2412 view_driver(struct view *view, enum request request)
2413 {
2414         int i;
2415
2416         if (request == REQ_NONE) {
2417                 doupdate();
2418                 return TRUE;
2419         }
2420
2421         if (request > REQ_NONE) {
2422                 open_run_request(request);
2423                 return TRUE;
2424         }
2425
2426         if (view && view->lines) {
2427                 request = view->ops->request(view, request, &view->line[view->lineno]);
2428                 if (request == REQ_NONE)
2429                         return TRUE;
2430         }
2431
2432         switch (request) {
2433         case REQ_MOVE_UP:
2434         case REQ_MOVE_DOWN:
2435         case REQ_MOVE_PAGE_UP:
2436         case REQ_MOVE_PAGE_DOWN:
2437         case REQ_MOVE_FIRST_LINE:
2438         case REQ_MOVE_LAST_LINE:
2439                 move_view(view, request);
2440                 break;
2441
2442         case REQ_SCROLL_LINE_DOWN:
2443         case REQ_SCROLL_LINE_UP:
2444         case REQ_SCROLL_PAGE_DOWN:
2445         case REQ_SCROLL_PAGE_UP:
2446                 scroll_view(view, request);
2447                 break;
2448
2449         case REQ_VIEW_BLOB:
2450                 if (!ref_blob[0]) {
2451                         report("No file chosen, press %s to open tree view",
2452                                get_key(REQ_VIEW_TREE));
2453                         break;
2454                 }
2455                 open_view(view, request, OPEN_DEFAULT);
2456                 break;
2457
2458         case REQ_VIEW_PAGER:
2459                 if (!opt_pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
2460                         report("No pager content, press %s to run command from prompt",
2461                                get_key(REQ_PROMPT));
2462                         break;
2463                 }
2464                 open_view(view, request, OPEN_DEFAULT);
2465                 break;
2466
2467         case REQ_VIEW_STAGE:
2468                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
2469                         report("No stage content, press %s to open the status view and choose file",
2470                                get_key(REQ_VIEW_STATUS));
2471                         break;
2472                 }
2473                 open_view(view, request, OPEN_DEFAULT);
2474                 break;
2475
2476         case REQ_VIEW_STATUS:
2477                 if (opt_is_inside_work_tree == FALSE) {
2478                         report("The status view requires a working tree");
2479                         break;
2480                 }
2481                 open_view(view, request, OPEN_DEFAULT);
2482                 break;
2483
2484         case REQ_VIEW_MAIN:
2485         case REQ_VIEW_DIFF:
2486         case REQ_VIEW_LOG:
2487         case REQ_VIEW_TREE:
2488         case REQ_VIEW_HELP:
2489                 open_view(view, request, OPEN_DEFAULT);
2490                 break;
2491
2492         case REQ_NEXT:
2493         case REQ_PREVIOUS:
2494                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
2495
2496                 if ((view == VIEW(REQ_VIEW_DIFF) &&
2497                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
2498                    (view == VIEW(REQ_VIEW_STAGE) &&
2499                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
2500                    (view == VIEW(REQ_VIEW_BLOB) &&
2501                      view->parent == VIEW(REQ_VIEW_TREE))) {
2502                         int line;
2503
2504                         view = view->parent;
2505                         line = view->lineno;
2506                         move_view(view, request);
2507                         if (view_is_displayed(view))
2508                                 update_view_title(view);
2509                         if (line != view->lineno)
2510                                 view->ops->request(view, REQ_ENTER,
2511                                                    &view->line[view->lineno]);
2512
2513                 } else {
2514                         move_view(view, request);
2515                 }
2516                 break;
2517
2518         case REQ_VIEW_NEXT:
2519         {
2520                 int nviews = displayed_views();
2521                 int next_view = (current_view + 1) % nviews;
2522
2523                 if (next_view == current_view) {
2524                         report("Only one view is displayed");
2525                         break;
2526                 }
2527
2528                 current_view = next_view;
2529                 /* Blur out the title of the previous view. */
2530                 update_view_title(view);
2531                 report("");
2532                 break;
2533         }
2534         case REQ_REFRESH:
2535                 report("Refreshing is not yet supported for the %s view", view->name);
2536                 break;
2537
2538         case REQ_TOGGLE_LINENO:
2539                 opt_line_number = !opt_line_number;
2540                 redraw_display();
2541                 break;
2542
2543         case REQ_TOGGLE_REV_GRAPH:
2544                 opt_rev_graph = !opt_rev_graph;
2545                 redraw_display();
2546                 break;
2547
2548         case REQ_PROMPT:
2549                 /* Always reload^Wrerun commands from the prompt. */
2550                 open_view(view, opt_request, OPEN_RELOAD);
2551                 break;
2552
2553         case REQ_SEARCH:
2554         case REQ_SEARCH_BACK:
2555                 search_view(view, request);
2556                 break;
2557
2558         case REQ_FIND_NEXT:
2559         case REQ_FIND_PREV:
2560                 find_next(view, request);
2561                 break;
2562
2563         case REQ_STOP_LOADING:
2564                 for (i = 0; i < ARRAY_SIZE(views); i++) {
2565                         view = &views[i];
2566                         if (view->pipe)
2567                                 report("Stopped loading the %s view", view->name),
2568                         end_update(view);
2569                 }
2570                 break;
2571
2572         case REQ_SHOW_VERSION:
2573                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
2574                 return TRUE;
2575
2576         case REQ_SCREEN_RESIZE:
2577                 resize_display();
2578                 /* Fall-through */
2579         case REQ_SCREEN_REDRAW:
2580                 redraw_display();
2581                 break;
2582
2583         case REQ_EDIT:
2584                 report("Nothing to edit");
2585                 break;
2586
2587
2588         case REQ_ENTER:
2589                 report("Nothing to enter");
2590                 break;
2591
2592
2593         case REQ_VIEW_CLOSE:
2594                 /* XXX: Mark closed views by letting view->parent point to the
2595                  * view itself. Parents to closed view should never be
2596                  * followed. */
2597                 if (view->parent &&
2598                     view->parent->parent != view->parent) {
2599                         memset(display, 0, sizeof(display));
2600                         current_view = 0;
2601                         display[current_view] = view->parent;
2602                         view->parent = view;
2603                         resize_display();
2604                         redraw_display();
2605                         break;
2606                 }
2607                 /* Fall-through */
2608         case REQ_QUIT:
2609                 return FALSE;
2610
2611         default:
2612                 /* An unknown key will show most commonly used commands. */
2613                 report("Unknown key, press 'h' for help");
2614                 return TRUE;
2615         }
2616
2617         return TRUE;
2618 }
2619
2620
2621 /*
2622  * Pager backend
2623  */
2624
2625 static bool
2626 pager_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
2627 {
2628         char *text = line->data;
2629         enum line_type type = line->type;
2630         int attr;
2631
2632         wmove(view->win, lineno, 0);
2633
2634         if (selected) {
2635                 type = LINE_CURSOR;
2636                 wchgat(view->win, -1, 0, type, NULL);
2637         }
2638
2639         attr = get_line_attr(type);
2640         wattrset(view->win, attr);
2641
2642         if (opt_line_number || opt_tab_size < TABSIZE) {
2643                 static char spaces[] = "                    ";
2644                 int col_offset = 0, col = 0;
2645
2646                 if (opt_line_number) {
2647                         unsigned long real_lineno = view->offset + lineno + 1;
2648
2649                         if (real_lineno == 1 ||
2650                             (real_lineno % opt_num_interval) == 0) {
2651                                 wprintw(view->win, "%.*d", view->digits, real_lineno);
2652
2653                         } else {
2654                                 waddnstr(view->win, spaces,
2655                                          MIN(view->digits, STRING_SIZE(spaces)));
2656                         }
2657                         waddstr(view->win, ": ");
2658                         col_offset = view->digits + 2;
2659                 }
2660
2661                 while (text && col_offset + col < view->width) {
2662                         int cols_max = view->width - col_offset - col;
2663                         char *pos = text;
2664                         int cols;
2665
2666                         if (*text == '\t') {
2667                                 text++;
2668                                 assert(sizeof(spaces) > TABSIZE);
2669                                 pos = spaces;
2670                                 cols = opt_tab_size - (col % opt_tab_size);
2671
2672                         } else {
2673                                 text = strchr(text, '\t');
2674                                 cols = line ? text - pos : strlen(pos);
2675                         }
2676
2677                         waddnstr(view->win, pos, MIN(cols, cols_max));
2678                         col += cols;
2679                 }
2680
2681         } else {
2682                 int tilde_attr = get_line_attr(LINE_MAIN_DELIM);
2683
2684                 draw_text(view, text, view->width, 0, TRUE, tilde_attr);
2685         }
2686
2687         return TRUE;
2688 }
2689
2690 static bool
2691 add_describe_ref(char *buf, size_t *bufpos, char *commit_id, const char *sep)
2692 {
2693         char refbuf[SIZEOF_STR];
2694         char *ref = NULL;
2695         FILE *pipe;
2696
2697         if (!string_format(refbuf, "git describe %s 2>/dev/null", commit_id))
2698                 return TRUE;
2699
2700         pipe = popen(refbuf, "r");
2701         if (!pipe)
2702                 return TRUE;
2703
2704         if ((ref = fgets(refbuf, sizeof(refbuf), pipe)))
2705                 ref = chomp_string(ref);
2706         pclose(pipe);
2707
2708         if (!ref || !*ref)
2709                 return TRUE;
2710
2711         /* This is the only fatal call, since it can "corrupt" the buffer. */
2712         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
2713                 return FALSE;
2714
2715         return TRUE;
2716 }
2717
2718 static void
2719 add_pager_refs(struct view *view, struct line *line)
2720 {
2721         char buf[SIZEOF_STR];
2722         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
2723         struct ref **refs;
2724         size_t bufpos = 0, refpos = 0;
2725         const char *sep = "Refs: ";
2726         bool is_tag = FALSE;
2727
2728         assert(line->type == LINE_COMMIT);
2729
2730         refs = get_refs(commit_id);
2731         if (!refs) {
2732                 if (view == VIEW(REQ_VIEW_DIFF))
2733                         goto try_add_describe_ref;
2734                 return;
2735         }
2736
2737         do {
2738                 struct ref *ref = refs[refpos];
2739                 char *fmt = ref->tag    ? "%s[%s]" :
2740                             ref->remote ? "%s<%s>" : "%s%s";
2741
2742                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
2743                         return;
2744                 sep = ", ";
2745                 if (ref->tag)
2746                         is_tag = TRUE;
2747         } while (refs[refpos++]->next);
2748
2749         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
2750 try_add_describe_ref:
2751                 /* Add <tag>-g<commit_id> "fake" reference. */
2752                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
2753                         return;
2754         }
2755
2756         if (bufpos == 0)
2757                 return;
2758
2759         if (!realloc_lines(view, view->line_size + 1))
2760                 return;
2761
2762         add_line_text(view, buf, LINE_PP_REFS);
2763 }
2764
2765 static bool
2766 pager_read(struct view *view, char *data)
2767 {
2768         struct line *line;
2769
2770         if (!data)
2771                 return TRUE;
2772
2773         line = add_line_text(view, data, get_line_type(data));
2774         if (!line)
2775                 return FALSE;
2776
2777         if (line->type == LINE_COMMIT &&
2778             (view == VIEW(REQ_VIEW_DIFF) ||
2779              view == VIEW(REQ_VIEW_LOG)))
2780                 add_pager_refs(view, line);
2781
2782         return TRUE;
2783 }
2784
2785 static enum request
2786 pager_request(struct view *view, enum request request, struct line *line)
2787 {
2788         int split = 0;
2789
2790         if (request != REQ_ENTER)
2791                 return request;
2792
2793         if (line->type == LINE_COMMIT &&
2794            (view == VIEW(REQ_VIEW_LOG) ||
2795             view == VIEW(REQ_VIEW_PAGER))) {
2796                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
2797                 split = 1;
2798         }
2799
2800         /* Always scroll the view even if it was split. That way
2801          * you can use Enter to scroll through the log view and
2802          * split open each commit diff. */
2803         scroll_view(view, REQ_SCROLL_LINE_DOWN);
2804
2805         /* FIXME: A minor workaround. Scrolling the view will call report("")
2806          * but if we are scrolling a non-current view this won't properly
2807          * update the view title. */
2808         if (split)
2809                 update_view_title(view);
2810
2811         return REQ_NONE;
2812 }
2813
2814 static bool
2815 pager_grep(struct view *view, struct line *line)
2816 {
2817         regmatch_t pmatch;
2818         char *text = line->data;
2819
2820         if (!*text)
2821                 return FALSE;
2822
2823         if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
2824                 return FALSE;
2825
2826         return TRUE;
2827 }
2828
2829 static void
2830 pager_select(struct view *view, struct line *line)
2831 {
2832         if (line->type == LINE_COMMIT) {
2833                 char *text = (char *)line->data + STRING_SIZE("commit ");
2834
2835                 if (view != VIEW(REQ_VIEW_PAGER))
2836                         string_copy_rev(view->ref, text);
2837                 string_copy_rev(ref_commit, text);
2838         }
2839 }
2840
2841 static struct view_ops pager_ops = {
2842         "line",
2843         NULL,
2844         pager_read,
2845         pager_draw,
2846         pager_request,
2847         pager_grep,
2848         pager_select,
2849 };
2850
2851
2852 /*
2853  * Help backend
2854  */
2855
2856 static bool
2857 help_open(struct view *view)
2858 {
2859         char buf[BUFSIZ];
2860         int lines = ARRAY_SIZE(req_info) + 2;
2861         int i;
2862
2863         if (view->lines > 0)
2864                 return TRUE;
2865
2866         for (i = 0; i < ARRAY_SIZE(req_info); i++)
2867                 if (!req_info[i].request)
2868                         lines++;
2869
2870         lines += run_requests + 1;
2871
2872         view->line = calloc(lines, sizeof(*view->line));
2873         if (!view->line)
2874                 return FALSE;
2875
2876         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
2877
2878         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
2879                 char *key;
2880
2881                 if (req_info[i].request == REQ_NONE)
2882                         continue;
2883
2884                 if (!req_info[i].request) {
2885                         add_line_text(view, "", LINE_DEFAULT);
2886                         add_line_text(view, req_info[i].help, LINE_DEFAULT);
2887                         continue;
2888                 }
2889
2890                 key = get_key(req_info[i].request);
2891                 if (!*key)
2892                         key = "(no key defined)";
2893
2894                 if (!string_format(buf, "    %-25s %s", key, req_info[i].help))
2895                         continue;
2896
2897                 add_line_text(view, buf, LINE_DEFAULT);
2898         }
2899
2900         if (run_requests) {
2901                 add_line_text(view, "", LINE_DEFAULT);
2902                 add_line_text(view, "External commands:", LINE_DEFAULT);
2903         }
2904
2905         for (i = 0; i < run_requests; i++) {
2906                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
2907                 char *key;
2908
2909                 if (!req)
2910                         continue;
2911
2912                 key = get_key_name(req->key);
2913                 if (!*key)
2914                         key = "(no key defined)";
2915
2916                 if (!string_format(buf, "    %-10s %-14s `%s`",
2917                                    keymap_table[req->keymap].name,
2918                                    key, req->cmd))
2919                         continue;
2920
2921                 add_line_text(view, buf, LINE_DEFAULT);
2922         }
2923
2924         return TRUE;
2925 }
2926
2927 static struct view_ops help_ops = {
2928         "line",
2929         help_open,
2930         NULL,
2931         pager_draw,
2932         pager_request,
2933         pager_grep,
2934         pager_select,
2935 };
2936
2937
2938 /*
2939  * Tree backend
2940  */
2941
2942 struct tree_stack_entry {
2943         struct tree_stack_entry *prev;  /* Entry below this in the stack */
2944         unsigned long lineno;           /* Line number to restore */
2945         char *name;                     /* Position of name in opt_path */
2946 };
2947
2948 /* The top of the path stack. */
2949 static struct tree_stack_entry *tree_stack = NULL;
2950 unsigned long tree_lineno = 0;
2951
2952 static void
2953 pop_tree_stack_entry(void)
2954 {
2955         struct tree_stack_entry *entry = tree_stack;
2956
2957         tree_lineno = entry->lineno;
2958         entry->name[0] = 0;
2959         tree_stack = entry->prev;
2960         free(entry);
2961 }
2962
2963 static void
2964 push_tree_stack_entry(char *name, unsigned long lineno)
2965 {
2966         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
2967         size_t pathlen = strlen(opt_path);
2968
2969         if (!entry)
2970                 return;
2971
2972         entry->prev = tree_stack;
2973         entry->name = opt_path + pathlen;
2974         tree_stack = entry;
2975
2976         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
2977                 pop_tree_stack_entry();
2978                 return;
2979         }
2980
2981         /* Move the current line to the first tree entry. */
2982         tree_lineno = 1;
2983         entry->lineno = lineno;
2984 }
2985
2986 /* Parse output from git-ls-tree(1):
2987  *
2988  * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
2989  * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
2990  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
2991  * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
2992  */
2993
2994 #define SIZEOF_TREE_ATTR \
2995         STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
2996
2997 #define TREE_UP_FORMAT "040000 tree %s\t.."
2998
2999 static int
3000 tree_compare_entry(enum line_type type1, char *name1,
3001                    enum line_type type2, char *name2)
3002 {
3003         if (type1 != type2) {
3004                 if (type1 == LINE_TREE_DIR)
3005                         return -1;
3006                 return 1;
3007         }
3008
3009         return strcmp(name1, name2);
3010 }
3011
3012 static bool
3013 tree_read(struct view *view, char *text)
3014 {
3015         size_t textlen = text ? strlen(text) : 0;
3016         char buf[SIZEOF_STR];
3017         unsigned long pos;
3018         enum line_type type;
3019         bool first_read = view->lines == 0;
3020
3021         if (textlen <= SIZEOF_TREE_ATTR)
3022                 return FALSE;
3023
3024         type = text[STRING_SIZE("100644 ")] == 't'
3025              ? LINE_TREE_DIR : LINE_TREE_FILE;
3026
3027         if (first_read) {
3028                 /* Add path info line */
3029                 if (!string_format(buf, "Directory path /%s", opt_path) ||
3030                     !realloc_lines(view, view->line_size + 1) ||
3031                     !add_line_text(view, buf, LINE_DEFAULT))
3032                         return FALSE;
3033
3034                 /* Insert "link" to parent directory. */
3035                 if (*opt_path) {
3036                         if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
3037                             !realloc_lines(view, view->line_size + 1) ||
3038                             !add_line_text(view, buf, LINE_TREE_DIR))
3039                                 return FALSE;
3040                 }
3041         }
3042
3043         /* Strip the path part ... */
3044         if (*opt_path) {
3045                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3046                 size_t striplen = strlen(opt_path);
3047                 char *path = text + SIZEOF_TREE_ATTR;
3048
3049                 if (pathlen > striplen)
3050                         memmove(path, path + striplen,
3051                                 pathlen - striplen + 1);
3052         }
3053
3054         /* Skip "Directory ..." and ".." line. */
3055         for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
3056                 struct line *line = &view->line[pos];
3057                 char *path1 = ((char *) line->data) + SIZEOF_TREE_ATTR;
3058                 char *path2 = text + SIZEOF_TREE_ATTR;
3059                 int cmp = tree_compare_entry(line->type, path1, type, path2);
3060
3061                 if (cmp <= 0)
3062                         continue;
3063
3064                 text = strdup(text);
3065                 if (!text)
3066                         return FALSE;
3067
3068                 if (view->lines > pos)
3069                         memmove(&view->line[pos + 1], &view->line[pos],
3070                                 (view->lines - pos) * sizeof(*line));
3071
3072                 line = &view->line[pos];
3073                 line->data = text;
3074                 line->type = type;
3075                 view->lines++;
3076                 return TRUE;
3077         }
3078
3079         if (!add_line_text(view, text, type))
3080                 return FALSE;
3081
3082         if (tree_lineno > view->lineno) {
3083                 view->lineno = tree_lineno;
3084                 tree_lineno = 0;
3085         }
3086
3087         return TRUE;
3088 }
3089
3090 static enum request
3091 tree_request(struct view *view, enum request request, struct line *line)
3092 {
3093         enum open_flags flags;
3094
3095         if (request == REQ_TREE_PARENT) {
3096                 if (*opt_path) {
3097                         /* fake 'cd  ..' */
3098                         request = REQ_ENTER;
3099                         line = &view->line[1];
3100                 } else {
3101                         /* quit view if at top of tree */
3102                         return REQ_VIEW_CLOSE;
3103                 }
3104         }
3105         if (request != REQ_ENTER)
3106                 return request;
3107
3108         /* Cleanup the stack if the tree view is at a different tree. */
3109         while (!*opt_path && tree_stack)
3110                 pop_tree_stack_entry();
3111
3112         switch (line->type) {
3113         case LINE_TREE_DIR:
3114                 /* Depending on whether it is a subdir or parent (updir?) link
3115                  * mangle the path buffer. */
3116                 if (line == &view->line[1] && *opt_path) {
3117                         pop_tree_stack_entry();
3118
3119                 } else {
3120                         char *data = line->data;
3121                         char *basename = data + SIZEOF_TREE_ATTR;
3122
3123                         push_tree_stack_entry(basename, view->lineno);
3124                 }
3125
3126                 /* Trees and subtrees share the same ID, so they are not not
3127                  * unique like blobs. */
3128                 flags = OPEN_RELOAD;
3129                 request = REQ_VIEW_TREE;
3130                 break;
3131
3132         case LINE_TREE_FILE:
3133                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3134                 request = REQ_VIEW_BLOB;
3135                 break;
3136
3137         default:
3138                 return TRUE;
3139         }
3140
3141         open_view(view, request, flags);
3142         if (request == REQ_VIEW_TREE) {
3143                 view->lineno = tree_lineno;
3144         }
3145
3146         return REQ_NONE;
3147 }
3148
3149 static void
3150 tree_select(struct view *view, struct line *line)
3151 {
3152         char *text = (char *)line->data + STRING_SIZE("100644 blob ");
3153
3154         if (line->type == LINE_TREE_FILE) {
3155                 string_copy_rev(ref_blob, text);
3156
3157         } else if (line->type != LINE_TREE_DIR) {
3158                 return;
3159         }
3160
3161         string_copy_rev(view->ref, text);
3162 }
3163
3164 static struct view_ops tree_ops = {
3165         "file",
3166         NULL,
3167         tree_read,
3168         pager_draw,
3169         tree_request,
3170         pager_grep,
3171         tree_select,
3172 };
3173
3174 static bool
3175 blob_read(struct view *view, char *line)
3176 {
3177         return add_line_text(view, line, LINE_DEFAULT) != NULL;
3178 }
3179
3180 static struct view_ops blob_ops = {
3181         "line",
3182         NULL,
3183         blob_read,
3184         pager_draw,
3185         pager_request,
3186         pager_grep,
3187         pager_select,
3188 };
3189
3190
3191 /*
3192  * Status backend
3193  */
3194
3195 struct status {
3196         char status;
3197         struct {
3198                 mode_t mode;
3199                 char rev[SIZEOF_REV];
3200                 char name[SIZEOF_STR];
3201         } old;
3202         struct {
3203                 mode_t mode;
3204                 char rev[SIZEOF_REV];
3205                 char name[SIZEOF_STR];
3206         } new;
3207 };
3208
3209 static struct status stage_status;
3210 static enum line_type stage_line_type;
3211
3212 /* Get fields from the diff line:
3213  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
3214  */
3215 static inline bool
3216 status_get_diff(struct status *file, char *buf, size_t bufsize)
3217 {
3218         char *old_mode = buf +  1;
3219         char *new_mode = buf +  8;
3220         char *old_rev  = buf + 15;
3221         char *new_rev  = buf + 56;
3222         char *status   = buf + 97;
3223
3224         if (bufsize < 99 ||
3225             old_mode[-1] != ':' ||
3226             new_mode[-1] != ' ' ||
3227             old_rev[-1]  != ' ' ||
3228             new_rev[-1]  != ' ' ||
3229             status[-1]   != ' ')
3230                 return FALSE;
3231
3232         file->status = *status;
3233
3234         string_copy_rev(file->old.rev, old_rev);
3235         string_copy_rev(file->new.rev, new_rev);
3236
3237         file->old.mode = strtoul(old_mode, NULL, 8);
3238         file->new.mode = strtoul(new_mode, NULL, 8);
3239
3240         file->old.name[0] = file->new.name[0] = 0;
3241
3242         return TRUE;
3243 }
3244
3245 static bool
3246 status_run(struct view *view, const char cmd[], bool diff, enum line_type type)
3247 {
3248         struct status *file = NULL;
3249         struct status *unmerged = NULL;
3250         char buf[SIZEOF_STR * 4];
3251         size_t bufsize = 0;
3252         FILE *pipe;
3253
3254         pipe = popen(cmd, "r");
3255         if (!pipe)
3256                 return FALSE;
3257
3258         add_line_data(view, NULL, type);
3259
3260         while (!feof(pipe) && !ferror(pipe)) {
3261                 char *sep;
3262                 size_t readsize;
3263
3264                 readsize = fread(buf + bufsize, 1, sizeof(buf) - bufsize, pipe);
3265                 if (!readsize)
3266                         break;
3267                 bufsize += readsize;
3268
3269                 /* Process while we have NUL chars. */
3270                 while ((sep = memchr(buf, 0, bufsize))) {
3271                         size_t sepsize = sep - buf + 1;
3272
3273                         if (!file) {
3274                                 if (!realloc_lines(view, view->line_size + 1))
3275                                         goto error_out;
3276
3277                                 file = calloc(1, sizeof(*file));
3278                                 if (!file)
3279                                         goto error_out;
3280
3281                                 add_line_data(view, file, type);
3282                         }
3283
3284                         /* Parse diff info part. */
3285                         if (!diff) {
3286                                 file->status = '?';
3287
3288                         } else if (!file->status) {
3289                                 if (!status_get_diff(file, buf, sepsize))
3290                                         goto error_out;
3291
3292                                 bufsize -= sepsize;
3293                                 memmove(buf, sep + 1, bufsize);
3294
3295                                 sep = memchr(buf, 0, bufsize);
3296                                 if (!sep)
3297                                         break;
3298                                 sepsize = sep - buf + 1;
3299
3300                                 /* Collapse all 'M'odified entries that
3301                                  * follow a associated 'U'nmerged entry.
3302                                  */
3303                                 if (file->status == 'U') {
3304                                         unmerged = file;
3305
3306                                 } else if (unmerged) {
3307                                         int collapse = !strcmp(buf, unmerged->new.name);
3308
3309                                         unmerged = NULL;
3310                                         if (collapse) {
3311                                                 free(file);
3312                                                 view->lines--;
3313                                                 continue;
3314                                         }
3315                                 }
3316                         }
3317
3318                         /* Grab the old name for rename/copy. */
3319                         if (!*file->old.name &&
3320                             (file->status == 'R' || file->status == 'C')) {
3321                                 sepsize = sep - buf + 1;
3322                                 string_ncopy(file->old.name, buf, sepsize);
3323                                 bufsize -= sepsize;
3324                                 memmove(buf, sep + 1, bufsize);
3325
3326                                 sep = memchr(buf, 0, bufsize);
3327                                 if (!sep)
3328                                         break;
3329                                 sepsize = sep - buf + 1;
3330                         }
3331
3332                         /* git-ls-files just delivers a NUL separated
3333                          * list of file names similar to the second half
3334                          * of the git-diff-* output. */
3335                         string_ncopy(file->new.name, buf, sepsize);
3336                         if (!*file->old.name)
3337                                 string_copy(file->old.name, file->new.name);
3338                         bufsize -= sepsize;
3339                         memmove(buf, sep + 1, bufsize);
3340                         file = NULL;
3341                 }
3342         }
3343
3344         if (ferror(pipe)) {
3345 error_out:
3346                 pclose(pipe);
3347                 return FALSE;
3348         }
3349
3350         if (!view->line[view->lines - 1].data)
3351                 add_line_data(view, NULL, LINE_STAT_NONE);
3352
3353         pclose(pipe);
3354         return TRUE;
3355 }
3356
3357 /* Don't show unmerged entries in the staged section. */
3358 #define STATUS_DIFF_INDEX_CMD "git diff-index -z --diff-filter=ACDMRTXB --cached -M HEAD"
3359 #define STATUS_DIFF_FILES_CMD "git update-index -q --refresh && git diff-files -z"
3360 #define STATUS_LIST_OTHER_CMD \
3361         "git ls-files -z --others --exclude-per-directory=.gitignore"
3362
3363 #define STATUS_DIFF_INDEX_SHOW_CMD \
3364         "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s %s 2>/dev/null"
3365
3366 #define STATUS_DIFF_FILES_SHOW_CMD \
3367         "git diff-files --root --patch-with-stat -C -M -- %s %s 2>/dev/null"
3368
3369 /* First parse staged info using git-diff-index(1), then parse unstaged
3370  * info using git-diff-files(1), and finally untracked files using
3371  * git-ls-files(1). */
3372 static bool
3373 status_open(struct view *view)
3374 {
3375         struct stat statbuf;
3376         char exclude[SIZEOF_STR];
3377         char cmd[SIZEOF_STR];
3378         unsigned long prev_lineno = view->lineno;
3379         size_t i;
3380
3381         for (i = 0; i < view->lines; i++)
3382                 free(view->line[i].data);
3383         free(view->line);
3384         view->lines = view->line_size = view->lineno = 0;
3385         view->line = NULL;
3386
3387         if (!realloc_lines(view, view->line_size + 6))
3388                 return FALSE;
3389
3390         if (!string_format(exclude, "%s/info/exclude", opt_git_dir))
3391                 return FALSE;
3392
3393         string_copy(cmd, STATUS_LIST_OTHER_CMD);
3394
3395         if (stat(exclude, &statbuf) >= 0) {
3396                 size_t cmdsize = strlen(cmd);
3397
3398                 if (!string_format_from(cmd, &cmdsize, " %s", "--exclude-from=") ||
3399                     sq_quote(cmd, cmdsize, exclude) >= sizeof(cmd))
3400                         return FALSE;
3401         }
3402
3403         if (!status_run(view, STATUS_DIFF_INDEX_CMD, TRUE, LINE_STAT_STAGED) ||
3404             !status_run(view, STATUS_DIFF_FILES_CMD, TRUE, LINE_STAT_UNSTAGED) ||
3405             !status_run(view, cmd, FALSE, LINE_STAT_UNTRACKED))
3406                 return FALSE;
3407
3408         /* If all went well restore the previous line number to stay in
3409          * the context. */
3410         if (prev_lineno < view->lines)
3411                 view->lineno = prev_lineno;
3412         else
3413                 view->lineno = view->lines - 1;
3414
3415         return TRUE;
3416 }
3417
3418 static bool
3419 status_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
3420 {
3421         struct status *status = line->data;
3422         int tilde_attr = get_line_attr(LINE_MAIN_DELIM);
3423
3424         wmove(view->win, lineno, 0);
3425
3426         if (selected) {
3427                 wattrset(view->win, get_line_attr(LINE_CURSOR));
3428                 wchgat(view->win, -1, 0, LINE_CURSOR, NULL);
3429                 tilde_attr = -1;
3430
3431         } else if (!status && line->type != LINE_STAT_NONE) {
3432                 wattrset(view->win, get_line_attr(LINE_STAT_SECTION));
3433                 wchgat(view->win, -1, 0, LINE_STAT_SECTION, NULL);
3434
3435         } else {
3436                 wattrset(view->win, get_line_attr(line->type));
3437         }
3438
3439         if (!status) {
3440                 char *text;
3441
3442                 switch (line->type) {
3443                 case LINE_STAT_STAGED:
3444                         text = "Changes to be committed:";
3445                         break;
3446
3447                 case LINE_STAT_UNSTAGED:
3448                         text = "Changed but not updated:";
3449                         break;
3450
3451                 case LINE_STAT_UNTRACKED:
3452                         text = "Untracked files:";
3453                         break;
3454
3455                 case LINE_STAT_NONE:
3456                         text = "    (no files)";
3457                         break;
3458
3459                 default:
3460                         return FALSE;
3461                 }
3462
3463                 draw_text(view, text, view->width, 0, TRUE, tilde_attr);
3464                 return TRUE;
3465         }
3466
3467         waddch(view->win, status->status);
3468         if (!selected)
3469                 wattrset(view->win, A_NORMAL);
3470         wmove(view->win, lineno, 4);
3471         if (view->width < 5)
3472                 return TRUE;
3473
3474         draw_text(view, status->new.name, view->width - 5, 5, TRUE, tilde_attr);
3475         return TRUE;
3476 }
3477
3478 static enum request
3479 status_enter(struct view *view, struct line *line)
3480 {
3481         struct status *status = line->data;
3482         char oldpath[SIZEOF_STR] = "";
3483         char newpath[SIZEOF_STR] = "";
3484         char *info;
3485         size_t cmdsize = 0;
3486
3487         if (line->type == LINE_STAT_NONE ||
3488             (!status && line[1].type == LINE_STAT_NONE)) {
3489                 report("No file to diff");
3490                 return REQ_NONE;
3491         }
3492
3493         if (status) {
3494                 if (sq_quote(oldpath, 0, status->old.name) >= sizeof(oldpath))
3495                         return REQ_QUIT;
3496                 /* Diffs for unmerged entries are empty when pasing the
3497                  * new path, so leave it empty. */
3498                 if (status->status != 'U' &&
3499                     sq_quote(newpath, 0, status->new.name) >= sizeof(newpath))
3500                         return REQ_QUIT;
3501         }
3502
3503         if (opt_cdup[0] &&
3504             line->type != LINE_STAT_UNTRACKED &&
3505             !string_format_from(opt_cmd, &cmdsize, "cd %s;", opt_cdup))
3506                 return REQ_QUIT;
3507
3508         switch (line->type) {
3509         case LINE_STAT_STAGED:
3510                 if (!string_format_from(opt_cmd, &cmdsize,
3511                                         STATUS_DIFF_INDEX_SHOW_CMD, oldpath, newpath))
3512                         return REQ_QUIT;
3513                 if (status)
3514                         info = "Staged changes to %s";
3515                 else
3516                         info = "Staged changes";
3517                 break;
3518
3519         case LINE_STAT_UNSTAGED:
3520                 if (!string_format_from(opt_cmd, &cmdsize,
3521                                         STATUS_DIFF_FILES_SHOW_CMD, oldpath, newpath))
3522                         return REQ_QUIT;
3523                 if (status)
3524                         info = "Unstaged changes to %s";
3525                 else
3526                         info = "Unstaged changes";
3527                 break;
3528
3529         case LINE_STAT_UNTRACKED:
3530                 if (opt_pipe)
3531                         return REQ_QUIT;
3532
3533
3534                 if (!status) {
3535                         report("No file to show");
3536                         return REQ_NONE;
3537                 }
3538
3539                 opt_pipe = fopen(status->new.name, "r");
3540                 info = "Untracked file %s";
3541                 break;
3542
3543         default:
3544                 die("line type %d not handled in switch", line->type);
3545         }
3546
3547         open_view(view, REQ_VIEW_STAGE, OPEN_RELOAD | OPEN_SPLIT);
3548         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
3549                 if (status) {
3550                         stage_status = *status;
3551                 } else {
3552                         memset(&stage_status, 0, sizeof(stage_status));
3553                 }
3554
3555                 stage_line_type = line->type;
3556                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
3557         }
3558
3559         return REQ_NONE;
3560 }
3561
3562
3563 static bool
3564 status_update_file(struct view *view, struct status *status, enum line_type type)
3565 {
3566         char cmd[SIZEOF_STR];
3567         char buf[SIZEOF_STR];
3568         size_t cmdsize = 0;
3569         size_t bufsize = 0;
3570         size_t written = 0;
3571         FILE *pipe;
3572
3573         if (opt_cdup[0] &&
3574             type != LINE_STAT_UNTRACKED &&
3575             !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
3576                 return FALSE;
3577
3578         switch (type) {
3579         case LINE_STAT_STAGED:
3580                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
3581                                         status->old.mode,
3582                                         status->old.rev,
3583                                         status->old.name, 0))
3584                         return FALSE;
3585
3586                 string_add(cmd, cmdsize, "git update-index -z --index-info");
3587                 break;
3588
3589         case LINE_STAT_UNSTAGED:
3590         case LINE_STAT_UNTRACKED:
3591                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
3592                         return FALSE;
3593
3594                 string_add(cmd, cmdsize, "git update-index -z --add --remove --stdin");
3595                 break;
3596
3597         default:
3598                 die("line type %d not handled in switch", type);
3599         }
3600
3601         pipe = popen(cmd, "w");
3602         if (!pipe)
3603                 return FALSE;
3604
3605         while (!ferror(pipe) && written < bufsize) {
3606                 written += fwrite(buf + written, 1, bufsize - written, pipe);
3607         }
3608
3609         pclose(pipe);
3610
3611         if (written != bufsize)
3612                 return FALSE;
3613
3614         return TRUE;
3615 }
3616
3617 static void
3618 status_update(struct view *view)
3619 {
3620         struct line *line = &view->line[view->lineno];
3621
3622         assert(view->lines);
3623
3624         if (!line->data) {
3625                 while (++line < view->line + view->lines && line->data) {
3626                         if (!status_update_file(view, line->data, line->type))
3627                                 report("Failed to update file status");
3628                 }
3629
3630                 if (!line[-1].data) {
3631                         report("Nothing to update");
3632                         return;
3633                 }
3634
3635         } else if (!status_update_file(view, line->data, line->type)) {
3636                 report("Failed to update file status");
3637         }
3638 }
3639
3640 static enum request
3641 status_request(struct view *view, enum request request, struct line *line)
3642 {
3643         struct status *status = line->data;
3644
3645         switch (request) {
3646         case REQ_STATUS_UPDATE:
3647                 status_update(view);
3648                 break;
3649
3650         case REQ_STATUS_MERGE:
3651                 if (!status || status->status != 'U') {
3652                         report("Merging only possible for files with unmerged status ('U').");
3653                         return REQ_NONE;
3654                 }
3655                 open_mergetool(status->new.name);
3656                 break;
3657
3658         case REQ_EDIT:
3659                 if (!status)
3660                         return request;
3661
3662                 open_editor(status->status != '?', status->new.name);
3663                 break;
3664
3665         case REQ_ENTER:
3666                 /* After returning the status view has been split to
3667                  * show the stage view. No further reloading is
3668                  * necessary. */
3669                 status_enter(view, line);
3670                 return REQ_NONE;
3671
3672         case REQ_REFRESH:
3673                 /* Simply reload the view. */
3674                 break;
3675
3676         default:
3677                 return request;
3678         }
3679
3680         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
3681
3682         return REQ_NONE;
3683 }
3684
3685 static void
3686 status_select(struct view *view, struct line *line)
3687 {
3688         struct status *status = line->data;
3689         char file[SIZEOF_STR] = "all files";
3690         char *text;
3691         char *key;
3692
3693         if (status && !string_format(file, "'%s'", status->new.name))
3694                 return;
3695
3696         if (!status && line[1].type == LINE_STAT_NONE)
3697                 line++;
3698
3699         switch (line->type) {
3700         case LINE_STAT_STAGED:
3701                 text = "Press %s to unstage %s for commit";
3702                 break;
3703
3704         case LINE_STAT_UNSTAGED:
3705                 text = "Press %s to stage %s for commit";
3706                 break;
3707
3708         case LINE_STAT_UNTRACKED:
3709                 text = "Press %s to stage %s for addition";
3710                 break;
3711
3712         case LINE_STAT_NONE:
3713                 text = "Nothing to update";
3714                 break;
3715
3716         default:
3717                 die("line type %d not handled in switch", line->type);
3718         }
3719
3720         if (status && status->status == 'U') {
3721                 text = "Press %s to resolve conflict in %s";
3722                 key = get_key(REQ_STATUS_MERGE);
3723
3724         } else {
3725                 key = get_key(REQ_STATUS_UPDATE);
3726         }
3727
3728         string_format(view->ref, text, key, file);
3729 }
3730
3731 static bool
3732 status_grep(struct view *view, struct line *line)
3733 {
3734         struct status *status = line->data;
3735         enum { S_STATUS, S_NAME, S_END } state;
3736         char buf[2] = "?";
3737         regmatch_t pmatch;
3738
3739         if (!status)
3740                 return FALSE;
3741
3742         for (state = S_STATUS; state < S_END; state++) {
3743                 char *text;
3744
3745                 switch (state) {
3746                 case S_NAME:    text = status->new.name;        break;
3747                 case S_STATUS:
3748                         buf[0] = status->status;
3749                         text = buf;
3750                         break;
3751
3752                 default:
3753                         return FALSE;
3754                 }
3755
3756                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
3757                         return TRUE;
3758         }
3759
3760         return FALSE;
3761 }
3762
3763 static struct view_ops status_ops = {
3764         "file",
3765         status_open,
3766         NULL,
3767         status_draw,
3768         status_request,
3769         status_grep,
3770         status_select,
3771 };
3772
3773
3774 static bool
3775 stage_diff_line(FILE *pipe, struct line *line)
3776 {
3777         char *buf = line->data;
3778         size_t bufsize = strlen(buf);
3779         size_t written = 0;
3780
3781         while (!ferror(pipe) && written < bufsize) {
3782                 written += fwrite(buf + written, 1, bufsize - written, pipe);
3783         }
3784
3785         fputc('\n', pipe);
3786
3787         return written == bufsize;
3788 }
3789
3790 static struct line *
3791 stage_diff_hdr(struct view *view, struct line *line)
3792 {
3793         int diff_hdr_dir = line->type == LINE_DIFF_CHUNK ? -1 : 1;
3794         struct line *diff_hdr;
3795
3796         if (line->type == LINE_DIFF_CHUNK)
3797                 diff_hdr = line - 1;
3798         else
3799                 diff_hdr = view->line + 1;
3800
3801         while (diff_hdr > view->line && diff_hdr < view->line + view->lines) {
3802                 if (diff_hdr->type == LINE_DIFF_HEADER)
3803                         return diff_hdr;
3804
3805                 diff_hdr += diff_hdr_dir;
3806         }
3807
3808         return NULL;
3809 }
3810
3811 static bool
3812 stage_update_chunk(struct view *view, struct line *line)
3813 {
3814         char cmd[SIZEOF_STR];
3815         size_t cmdsize = 0;
3816         struct line *diff_hdr, *diff_chunk, *diff_end;
3817         FILE *pipe;
3818
3819         diff_hdr = stage_diff_hdr(view, line);
3820         if (!diff_hdr)
3821                 return FALSE;
3822
3823         if (opt_cdup[0] &&
3824             !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
3825                 return FALSE;
3826
3827         if (!string_format_from(cmd, &cmdsize,
3828                                 "git apply --cached %s - && "
3829                                 "git update-index -q --unmerged --refresh 2>/dev/null",
3830                                 stage_line_type == LINE_STAT_STAGED ? "-R" : ""))
3831                 return FALSE;
3832
3833         pipe = popen(cmd, "w");
3834         if (!pipe)
3835                 return FALSE;
3836
3837         diff_end = view->line + view->lines;
3838         if (line->type != LINE_DIFF_CHUNK) {
3839                 diff_chunk = diff_hdr;
3840
3841         } else {
3842                 for (diff_chunk = line + 1; diff_chunk < diff_end; diff_chunk++)
3843                         if (diff_chunk->type == LINE_DIFF_CHUNK ||
3844                             diff_chunk->type == LINE_DIFF_HEADER)
3845                                 diff_end = diff_chunk;
3846
3847                 diff_chunk = line;
3848
3849                 while (diff_hdr->type != LINE_DIFF_CHUNK) {
3850                         switch (diff_hdr->type) {
3851                         case LINE_DIFF_HEADER:
3852                         case LINE_DIFF_INDEX:
3853                         case LINE_DIFF_ADD:
3854                         case LINE_DIFF_DEL:
3855                                 break;
3856
3857                         default:
3858                                 diff_hdr++;
3859                                 continue;
3860                         }
3861
3862                         if (!stage_diff_line(pipe, diff_hdr++)) {
3863                                 pclose(pipe);
3864                                 return FALSE;
3865                         }
3866                 }
3867         }
3868
3869         while (diff_chunk < diff_end && stage_diff_line(pipe, diff_chunk))
3870                 diff_chunk++;
3871
3872         pclose(pipe);
3873
3874         if (diff_chunk != diff_end)
3875                 return FALSE;
3876
3877         return TRUE;
3878 }
3879
3880 static void
3881 stage_update(struct view *view, struct line *line)
3882 {
3883         if (stage_line_type != LINE_STAT_UNTRACKED &&
3884             (line->type == LINE_DIFF_CHUNK || !stage_status.status)) {
3885                 if (!stage_update_chunk(view, line)) {
3886                         report("Failed to apply chunk");
3887                         return;
3888                 }
3889
3890         } else if (!status_update_file(view, &stage_status, stage_line_type)) {
3891                 report("Failed to update file");
3892                 return;
3893         }
3894
3895         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
3896
3897         view = VIEW(REQ_VIEW_STATUS);
3898         if (view_is_displayed(view))
3899                 status_enter(view, &view->line[view->lineno]);
3900 }
3901
3902 static enum request
3903 stage_request(struct view *view, enum request request, struct line *line)
3904 {
3905         switch (request) {
3906         case REQ_STATUS_UPDATE:
3907                 stage_update(view, line);
3908                 break;
3909
3910         case REQ_EDIT:
3911                 if (!stage_status.new.name[0])
3912                         return request;
3913
3914                 open_editor(stage_status.status != '?', stage_status.new.name);
3915                 break;
3916
3917         case REQ_ENTER:
3918                 pager_request(view, request, line);
3919                 break;
3920
3921         default:
3922                 return request;
3923         }
3924
3925         return REQ_NONE;
3926 }
3927
3928 static struct view_ops stage_ops = {
3929         "line",
3930         NULL,
3931         pager_read,
3932         pager_draw,
3933         stage_request,
3934         pager_grep,
3935         pager_select,
3936 };
3937
3938
3939 /*
3940  * Revision graph
3941  */
3942
3943 struct commit {
3944         char id[SIZEOF_REV];            /* SHA1 ID. */
3945         char title[128];                /* First line of the commit message. */
3946         char author[75];                /* Author of the commit. */
3947         struct tm time;                 /* Date from the author ident. */
3948         struct ref **refs;              /* Repository references. */
3949         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
3950         size_t graph_size;              /* The width of the graph array. */
3951 };
3952
3953 /* Size of rev graph with no  "padding" columns */
3954 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
3955
3956 struct rev_graph {
3957         struct rev_graph *prev, *next, *parents;
3958         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
3959         size_t size;
3960         struct commit *commit;
3961         size_t pos;
3962         unsigned int boundary:1;
3963 };
3964
3965 /* Parents of the commit being visualized. */
3966 static struct rev_graph graph_parents[4];
3967
3968 /* The current stack of revisions on the graph. */
3969 static struct rev_graph graph_stacks[4] = {
3970         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
3971         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
3972         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
3973         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
3974 };
3975
3976 static inline bool
3977 graph_parent_is_merge(struct rev_graph *graph)
3978 {
3979         return graph->parents->size > 1;
3980 }
3981
3982 static inline void
3983 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
3984 {
3985         struct commit *commit = graph->commit;
3986
3987         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
3988                 commit->graph[commit->graph_size++] = symbol;
3989 }
3990
3991 static void
3992 done_rev_graph(struct rev_graph *graph)
3993 {
3994         if (graph_parent_is_merge(graph) &&
3995             graph->pos < graph->size - 1 &&
3996             graph->next->size == graph->size + graph->parents->size - 1) {
3997                 size_t i = graph->pos + graph->parents->size - 1;
3998
3999                 graph->commit->graph_size = i * 2;
4000                 while (i < graph->next->size - 1) {
4001                         append_to_rev_graph(graph, ' ');
4002                         append_to_rev_graph(graph, '\\');
4003                         i++;
4004                 }
4005         }
4006
4007         graph->size = graph->pos = 0;
4008         graph->commit = NULL;
4009         memset(graph->parents, 0, sizeof(*graph->parents));
4010 }
4011
4012 static void
4013 push_rev_graph(struct rev_graph *graph, char *parent)
4014 {
4015         int i;
4016
4017         /* "Collapse" duplicate parents lines.
4018          *
4019          * FIXME: This needs to also update update the drawn graph but
4020          * for now it just serves as a method for pruning graph lines. */
4021         for (i = 0; i < graph->size; i++)
4022                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
4023                         return;
4024
4025         if (graph->size < SIZEOF_REVITEMS) {
4026                 string_copy_rev(graph->rev[graph->size++], parent);
4027         }
4028 }
4029
4030 static chtype
4031 get_rev_graph_symbol(struct rev_graph *graph)
4032 {
4033         chtype symbol;
4034
4035         if (graph->boundary)
4036                 symbol = REVGRAPH_BOUND;
4037         else if (graph->parents->size == 0)
4038                 symbol = REVGRAPH_INIT;
4039         else if (graph_parent_is_merge(graph))
4040                 symbol = REVGRAPH_MERGE;
4041         else if (graph->pos >= graph->size)
4042                 symbol = REVGRAPH_BRANCH;
4043         else
4044                 symbol = REVGRAPH_COMMIT;
4045
4046         return symbol;
4047 }
4048
4049 static void
4050 draw_rev_graph(struct rev_graph *graph)
4051 {
4052         struct rev_filler {
4053                 chtype separator, line;
4054         };
4055         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
4056         static struct rev_filler fillers[] = {
4057                 { ' ',  REVGRAPH_LINE },
4058                 { '`',  '.' },
4059                 { '\'', ' ' },
4060                 { '/',  ' ' },
4061         };
4062         chtype symbol = get_rev_graph_symbol(graph);
4063         struct rev_filler *filler;
4064         size_t i;
4065
4066         filler = &fillers[DEFAULT];
4067
4068         for (i = 0; i < graph->pos; i++) {
4069                 append_to_rev_graph(graph, filler->line);
4070                 if (graph_parent_is_merge(graph->prev) &&
4071                     graph->prev->pos == i)
4072                         filler = &fillers[RSHARP];
4073
4074                 append_to_rev_graph(graph, filler->separator);
4075         }
4076
4077         /* Place the symbol for this revision. */
4078         append_to_rev_graph(graph, symbol);
4079
4080         if (graph->prev->size > graph->size)
4081                 filler = &fillers[RDIAG];
4082         else
4083                 filler = &fillers[DEFAULT];
4084
4085         i++;
4086
4087         for (; i < graph->size; i++) {
4088                 append_to_rev_graph(graph, filler->separator);
4089                 append_to_rev_graph(graph, filler->line);
4090                 if (graph_parent_is_merge(graph->prev) &&
4091                     i < graph->prev->pos + graph->parents->size)
4092                         filler = &fillers[RSHARP];
4093                 if (graph->prev->size > graph->size)
4094                         filler = &fillers[LDIAG];
4095         }
4096
4097         if (graph->prev->size > graph->size) {
4098                 append_to_rev_graph(graph, filler->separator);
4099                 if (filler->line != ' ')
4100                         append_to_rev_graph(graph, filler->line);
4101         }
4102 }
4103
4104 /* Prepare the next rev graph */
4105 static void
4106 prepare_rev_graph(struct rev_graph *graph)
4107 {
4108         size_t i;
4109
4110         /* First, traverse all lines of revisions up to the active one. */
4111         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
4112                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
4113                         break;
4114
4115                 push_rev_graph(graph->next, graph->rev[graph->pos]);
4116         }
4117
4118         /* Interleave the new revision parent(s). */
4119         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
4120                 push_rev_graph(graph->next, graph->parents->rev[i]);
4121
4122         /* Lastly, put any remaining revisions. */
4123         for (i = graph->pos + 1; i < graph->size; i++)
4124                 push_rev_graph(graph->next, graph->rev[i]);
4125 }
4126
4127 static void
4128 update_rev_graph(struct rev_graph *graph)
4129 {
4130         /* If this is the finalizing update ... */
4131         if (graph->commit)
4132                 prepare_rev_graph(graph);
4133
4134         /* Graph visualization needs a one rev look-ahead,
4135          * so the first update doesn't visualize anything. */
4136         if (!graph->prev->commit)
4137                 return;
4138
4139         draw_rev_graph(graph->prev);
4140         done_rev_graph(graph->prev->prev);
4141 }
4142
4143
4144 /*
4145  * Main view backend
4146  */
4147
4148 static bool
4149 main_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
4150 {
4151         char buf[DATE_COLS + 1];
4152         struct commit *commit = line->data;
4153         enum line_type type;
4154         int col = 0;
4155         size_t timelen;
4156         int tilde_attr;
4157         int space;
4158
4159         if (!*commit->author)
4160                 return FALSE;
4161
4162         space = view->width;
4163         wmove(view->win, lineno, col);
4164
4165         if (selected) {
4166                 type = LINE_CURSOR;
4167                 wattrset(view->win, get_line_attr(type));
4168                 wchgat(view->win, -1, 0, type, NULL);
4169                 tilde_attr = -1;
4170         } else {
4171                 type = LINE_MAIN_COMMIT;
4172                 wattrset(view->win, get_line_attr(LINE_MAIN_DATE));
4173                 tilde_attr = get_line_attr(LINE_MAIN_DELIM);
4174         }
4175
4176         {
4177                 int n;
4178
4179                 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time);
4180                 n = draw_text(
4181                         view, buf, view->width - col, col, FALSE, tilde_attr);
4182                 draw_text(
4183                         view, " ", view->width - col - n, col + n, FALSE,
4184                         tilde_attr);
4185
4186                 col += DATE_COLS;
4187                 wmove(view->win, lineno, col);
4188                 if (col >= view->width)
4189                         return TRUE;
4190         }
4191         if (type != LINE_CURSOR)
4192                 wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR));
4193
4194         {
4195                 int max_len;
4196
4197                 max_len = view->width - col;
4198                 if (max_len > AUTHOR_COLS - 1)
4199                         max_len = AUTHOR_COLS - 1;
4200                 draw_text(
4201                         view, commit->author, max_len, col, TRUE, tilde_attr);
4202                 col += AUTHOR_COLS;
4203                 if (col >= view->width)
4204                         return TRUE;
4205         }
4206
4207         if (opt_rev_graph && commit->graph_size) {
4208                 size_t graph_size = view->width - col;
4209                 size_t i;
4210
4211                 if (type != LINE_CURSOR)
4212                         wattrset(view->win, get_line_attr(LINE_MAIN_REVGRAPH));
4213                 wmove(view->win, lineno, col);
4214                 if (graph_size > commit->graph_size)
4215                         graph_size = commit->graph_size;
4216                 /* Using waddch() instead of waddnstr() ensures that
4217                  * they'll be rendered correctly for the cursor line. */
4218                 for (i = 0; i < graph_size; i++)
4219                         waddch(view->win, commit->graph[i]);
4220
4221                 col += commit->graph_size + 1;
4222                 if (col >= view->width)
4223                         return TRUE;
4224                 waddch(view->win, ' ');
4225         }
4226         if (type != LINE_CURSOR)
4227                 wattrset(view->win, A_NORMAL);
4228
4229         wmove(view->win, lineno, col);
4230
4231         if (commit->refs) {
4232                 size_t i = 0;
4233
4234                 do {
4235                         if (type == LINE_CURSOR)
4236                                 ;
4237                         else if (commit->refs[i]->tag)
4238                                 wattrset(view->win, get_line_attr(LINE_MAIN_TAG));
4239                         else if (commit->refs[i]->remote)
4240                                 wattrset(view->win, get_line_attr(LINE_MAIN_REMOTE));
4241                         else
4242                                 wattrset(view->win, get_line_attr(LINE_MAIN_REF));
4243
4244                         col += draw_text(
4245                                 view, "[", view->width - col, col, TRUE,
4246                                 tilde_attr);
4247                         col += draw_text(
4248                                 view, commit->refs[i]->name, view->width - col,
4249                                 col, TRUE, tilde_attr);
4250                         col += draw_text(
4251                                 view, "]", view->width - col, col, TRUE,
4252                                 tilde_attr);
4253                         if (type != LINE_CURSOR)
4254                                 wattrset(view->win, A_NORMAL);
4255                         col += draw_text(
4256                                 view, " ", view->width - col, col, TRUE,
4257                                 tilde_attr);
4258                         if (col >= view->width)
4259                                 return TRUE;
4260                 } while (commit->refs[i++]->next);
4261         }
4262
4263         if (type != LINE_CURSOR)
4264                 wattrset(view->win, get_line_attr(type));
4265
4266         col += draw_text(
4267                 view, commit->title, view->width - col, col, TRUE, tilde_attr);
4268
4269         return TRUE;
4270 }
4271
4272 /* Reads git log --pretty=raw output and parses it into the commit struct. */
4273 static bool
4274 main_read(struct view *view, char *line)
4275 {
4276         static struct rev_graph *graph = graph_stacks;
4277         enum line_type type;
4278         struct commit *commit;
4279
4280         if (!line) {
4281                 update_rev_graph(graph);
4282                 return TRUE;
4283         }
4284
4285         type = get_line_type(line);
4286         if (type == LINE_COMMIT) {
4287                 commit = calloc(1, sizeof(struct commit));
4288                 if (!commit)
4289                         return FALSE;
4290
4291                 line += STRING_SIZE("commit ");
4292                 if (*line == '-') {
4293                         graph->boundary = 1;
4294                         line++;
4295                 }
4296
4297                 string_copy_rev(commit->id, line);
4298                 commit->refs = get_refs(commit->id);
4299                 graph->commit = commit;
4300                 add_line_data(view, commit, LINE_MAIN_COMMIT);
4301                 return TRUE;
4302         }
4303
4304         if (!view->lines)
4305                 return TRUE;
4306         commit = view->line[view->lines - 1].data;
4307
4308         switch (type) {
4309         case LINE_PARENT:
4310                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
4311                 break;
4312
4313         case LINE_AUTHOR:
4314         {
4315                 /* Parse author lines where the name may be empty:
4316                  *      author  <email@address.tld> 1138474660 +0100
4317                  */
4318                 char *ident = line + STRING_SIZE("author ");
4319                 char *nameend = strchr(ident, '<');
4320                 char *emailend = strchr(ident, '>');
4321
4322                 if (!nameend || !emailend)
4323                         break;
4324
4325                 update_rev_graph(graph);
4326                 graph = graph->next;
4327
4328                 *nameend = *emailend = 0;
4329                 ident = chomp_string(ident);
4330                 if (!*ident) {
4331                         ident = chomp_string(nameend + 1);
4332                         if (!*ident)
4333                                 ident = "Unknown";
4334                 }
4335
4336                 string_ncopy(commit->author, ident, strlen(ident));
4337
4338                 /* Parse epoch and timezone */
4339                 if (emailend[1] == ' ') {
4340                         char *secs = emailend + 2;
4341                         char *zone = strchr(secs, ' ');
4342                         time_t time = (time_t) atol(secs);
4343
4344                         if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
4345                                 long tz;
4346
4347                                 zone++;
4348                                 tz  = ('0' - zone[1]) * 60 * 60 * 10;
4349                                 tz += ('0' - zone[2]) * 60 * 60;
4350                                 tz += ('0' - zone[3]) * 60;
4351                                 tz += ('0' - zone[4]) * 60;
4352
4353                                 if (zone[0] == '-')
4354                                         tz = -tz;
4355
4356                                 time -= tz;
4357                         }
4358
4359                         gmtime_r(&time, &commit->time);
4360                 }
4361                 break;
4362         }
4363         default:
4364                 /* Fill in the commit title if it has not already been set. */
4365                 if (commit->title[0])
4366                         break;
4367
4368                 /* Require titles to start with a non-space character at the
4369                  * offset used by git log. */
4370                 if (strncmp(line, "    ", 4))
4371                         break;
4372                 line += 4;
4373                 /* Well, if the title starts with a whitespace character,
4374                  * try to be forgiving.  Otherwise we end up with no title. */
4375                 while (isspace(*line))
4376                         line++;
4377                 if (*line == '\0')
4378                         break;
4379                 /* FIXME: More graceful handling of titles; append "..." to
4380                  * shortened titles, etc. */
4381
4382                 string_ncopy(commit->title, line, strlen(line));
4383         }
4384
4385         return TRUE;
4386 }
4387
4388 static enum request
4389 main_request(struct view *view, enum request request, struct line *line)
4390 {
4391         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4392
4393         if (request == REQ_ENTER)
4394                 open_view(view, REQ_VIEW_DIFF, flags);
4395         else
4396                 return request;
4397
4398         return REQ_NONE;
4399 }
4400
4401 static bool
4402 main_grep(struct view *view, struct line *line)
4403 {
4404         struct commit *commit = line->data;
4405         enum { S_TITLE, S_AUTHOR, S_DATE, S_END } state;
4406         char buf[DATE_COLS + 1];
4407         regmatch_t pmatch;
4408
4409         for (state = S_TITLE; state < S_END; state++) {
4410                 char *text;
4411
4412                 switch (state) {
4413                 case S_TITLE:   text = commit->title;   break;
4414                 case S_AUTHOR:  text = commit->author;  break;
4415                 case S_DATE:
4416                         if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
4417                                 continue;
4418                         text = buf;
4419                         break;
4420
4421                 default:
4422                         return FALSE;
4423                 }
4424
4425                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4426                         return TRUE;
4427         }
4428
4429         return FALSE;
4430 }
4431
4432 static void
4433 main_select(struct view *view, struct line *line)
4434 {
4435         struct commit *commit = line->data;
4436
4437         string_copy_rev(view->ref, commit->id);
4438         string_copy_rev(ref_commit, view->ref);
4439 }
4440
4441 static struct view_ops main_ops = {
4442         "commit",
4443         NULL,
4444         main_read,
4445         main_draw,
4446         main_request,
4447         main_grep,
4448         main_select,
4449 };
4450
4451
4452 /*
4453  * Unicode / UTF-8 handling
4454  *
4455  * NOTE: Much of the following code for dealing with unicode is derived from
4456  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
4457  * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
4458  */
4459
4460 /* I've (over)annotated a lot of code snippets because I am not entirely
4461  * confident that the approach taken by this small UTF-8 interface is correct.
4462  * --jonas */
4463
4464 static inline int
4465 unicode_width(unsigned long c)
4466 {
4467         if (c >= 0x1100 &&
4468            (c <= 0x115f                         /* Hangul Jamo */
4469             || c == 0x2329
4470             || c == 0x232a
4471             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
4472                                                 /* CJK ... Yi */
4473             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
4474             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
4475             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
4476             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
4477             || (c >= 0xffe0  && c <= 0xffe6)
4478             || (c >= 0x20000 && c <= 0x2fffd)
4479             || (c >= 0x30000 && c <= 0x3fffd)))
4480                 return 2;
4481
4482         if (c == '\t')
4483                 return opt_tab_size;
4484
4485         return 1;
4486 }
4487
4488 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
4489  * Illegal bytes are set one. */
4490 static const unsigned char utf8_bytes[256] = {
4491         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,
4492         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,
4493         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,
4494         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,
4495         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,
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         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,
4498         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,
4499 };
4500
4501 /* Decode UTF-8 multi-byte representation into a unicode character. */
4502 static inline unsigned long
4503 utf8_to_unicode(const char *string, size_t length)
4504 {
4505         unsigned long unicode;
4506
4507         switch (length) {
4508         case 1:
4509                 unicode  =   string[0];
4510                 break;
4511         case 2:
4512                 unicode  =  (string[0] & 0x1f) << 6;
4513                 unicode +=  (string[1] & 0x3f);
4514                 break;
4515         case 3:
4516                 unicode  =  (string[0] & 0x0f) << 12;
4517                 unicode += ((string[1] & 0x3f) << 6);
4518                 unicode +=  (string[2] & 0x3f);
4519                 break;
4520         case 4:
4521                 unicode  =  (string[0] & 0x0f) << 18;
4522                 unicode += ((string[1] & 0x3f) << 12);
4523                 unicode += ((string[2] & 0x3f) << 6);
4524                 unicode +=  (string[3] & 0x3f);
4525                 break;
4526         case 5:
4527                 unicode  =  (string[0] & 0x0f) << 24;
4528                 unicode += ((string[1] & 0x3f) << 18);
4529                 unicode += ((string[2] & 0x3f) << 12);
4530                 unicode += ((string[3] & 0x3f) << 6);
4531                 unicode +=  (string[4] & 0x3f);
4532                 break;
4533         case 6:
4534                 unicode  =  (string[0] & 0x01) << 30;
4535                 unicode += ((string[1] & 0x3f) << 24);
4536                 unicode += ((string[2] & 0x3f) << 18);
4537                 unicode += ((string[3] & 0x3f) << 12);
4538                 unicode += ((string[4] & 0x3f) << 6);
4539                 unicode +=  (string[5] & 0x3f);
4540                 break;
4541         default:
4542                 die("Invalid unicode length");
4543         }
4544
4545         /* Invalid characters could return the special 0xfffd value but NUL
4546          * should be just as good. */
4547         return unicode > 0xffff ? 0 : unicode;
4548 }
4549
4550 /* Calculates how much of string can be shown within the given maximum width
4551  * and sets trimmed parameter to non-zero value if all of string could not be
4552  * shown. If the reserve flag is TRUE, it will reserve at least one
4553  * trailing character, which can be useful when drawing a delimiter.
4554  *
4555  * Returns the number of bytes to output from string to satisfy max_width. */
4556 static size_t
4557 utf8_length(const char *string, size_t max_width, int *trimmed, bool reserve)
4558 {
4559         const char *start = string;
4560         const char *end = strchr(string, '\0');
4561         unsigned char last_bytes = 0;
4562         size_t width = 0;
4563
4564         *trimmed = 0;
4565
4566         while (string < end) {
4567                 int c = *(unsigned char *) string;
4568                 unsigned char bytes = utf8_bytes[c];
4569                 size_t ucwidth;
4570                 unsigned long unicode;
4571
4572                 if (string + bytes > end)
4573                         break;
4574
4575                 /* Change representation to figure out whether
4576                  * it is a single- or double-width character. */
4577
4578                 unicode = utf8_to_unicode(string, bytes);
4579                 /* FIXME: Graceful handling of invalid unicode character. */
4580                 if (!unicode)
4581                         break;
4582
4583                 ucwidth = unicode_width(unicode);
4584                 width  += ucwidth;
4585                 if (width > max_width) {
4586                         *trimmed = 1;
4587                         if (reserve && width - ucwidth == max_width) {
4588                                 string -= last_bytes;
4589                         }
4590                         break;
4591                 }
4592
4593                 string  += bytes;
4594                 last_bytes = bytes;
4595         }
4596
4597         return string - start;
4598 }
4599
4600
4601 /*
4602  * Status management
4603  */
4604
4605 /* Whether or not the curses interface has been initialized. */
4606 static bool cursed = FALSE;
4607
4608 /* The status window is used for polling keystrokes. */
4609 static WINDOW *status_win;
4610
4611 static bool status_empty = TRUE;
4612
4613 /* Update status and title window. */
4614 static void
4615 report(const char *msg, ...)
4616 {
4617         struct view *view = display[current_view];
4618
4619         if (input_mode)
4620                 return;
4621
4622         if (!view) {
4623                 char buf[SIZEOF_STR];
4624                 va_list args;
4625
4626                 va_start(args, msg);
4627                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
4628                         buf[sizeof(buf) - 1] = 0;
4629                         buf[sizeof(buf) - 2] = '.';
4630                         buf[sizeof(buf) - 3] = '.';
4631                         buf[sizeof(buf) - 4] = '.';
4632                 }
4633                 va_end(args);
4634                 die("%s", buf);
4635         }
4636
4637         if (!status_empty || *msg) {
4638                 va_list args;
4639
4640                 va_start(args, msg);
4641
4642                 wmove(status_win, 0, 0);
4643                 if (*msg) {
4644                         vwprintw(status_win, msg, args);
4645                         status_empty = FALSE;
4646                 } else {
4647                         status_empty = TRUE;
4648                 }
4649                 wclrtoeol(status_win);
4650                 wrefresh(status_win);
4651
4652                 va_end(args);
4653         }
4654
4655         update_view_title(view);
4656         update_display_cursor(view);
4657 }
4658
4659 /* Controls when nodelay should be in effect when polling user input. */
4660 static void
4661 set_nonblocking_input(bool loading)
4662 {
4663         static unsigned int loading_views;
4664
4665         if ((loading == FALSE && loading_views-- == 1) ||
4666             (loading == TRUE  && loading_views++ == 0))
4667                 nodelay(status_win, loading);
4668 }
4669
4670 static void
4671 init_display(void)
4672 {
4673         int x, y;
4674
4675         /* Initialize the curses library */
4676         if (isatty(STDIN_FILENO)) {
4677                 cursed = !!initscr();
4678         } else {
4679                 /* Leave stdin and stdout alone when acting as a pager. */
4680                 FILE *io = fopen("/dev/tty", "r+");
4681
4682                 if (!io)
4683                         die("Failed to open /dev/tty");
4684                 cursed = !!newterm(NULL, io, io);
4685         }
4686
4687         if (!cursed)
4688                 die("Failed to initialize curses");
4689
4690         nonl();         /* Tell curses not to do NL->CR/NL on output */
4691         cbreak();       /* Take input chars one at a time, no wait for \n */
4692         noecho();       /* Don't echo input */
4693         leaveok(stdscr, TRUE);
4694
4695         if (has_colors())
4696                 init_colors();
4697
4698         getmaxyx(stdscr, y, x);
4699         status_win = newwin(1, 0, y - 1, 0);
4700         if (!status_win)
4701                 die("Failed to create status window");
4702
4703         /* Enable keyboard mapping */
4704         keypad(status_win, TRUE);
4705         wbkgdset(status_win, get_line_attr(LINE_STATUS));
4706 }
4707
4708 static char *
4709 read_prompt(const char *prompt)
4710 {
4711         enum { READING, STOP, CANCEL } status = READING;
4712         static char buf[sizeof(opt_cmd) - STRING_SIZE("git \0")];
4713         int pos = 0;
4714
4715         while (status == READING) {
4716                 struct view *view;
4717                 int i, key;
4718
4719                 input_mode = TRUE;
4720
4721                 foreach_view (view, i)
4722                         update_view(view);
4723
4724                 input_mode = FALSE;
4725
4726                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
4727                 wclrtoeol(status_win);
4728
4729                 /* Refresh, accept single keystroke of input */
4730                 key = wgetch(status_win);
4731                 switch (key) {
4732                 case KEY_RETURN:
4733                 case KEY_ENTER:
4734                 case '\n':
4735                         status = pos ? STOP : CANCEL;
4736                         break;
4737
4738                 case KEY_BACKSPACE:
4739                         if (pos > 0)
4740                                 pos--;
4741                         else
4742                                 status = CANCEL;
4743                         break;
4744
4745                 case KEY_ESC:
4746                         status = CANCEL;
4747                         break;
4748
4749                 case ERR:
4750                         break;
4751
4752                 default:
4753                         if (pos >= sizeof(buf)) {
4754                                 report("Input string too long");
4755                                 return NULL;
4756                         }
4757
4758                         if (isprint(key))
4759                                 buf[pos++] = (char) key;
4760                 }
4761         }
4762
4763         /* Clear the status window */
4764         status_empty = FALSE;
4765         report("");
4766
4767         if (status == CANCEL)
4768                 return NULL;
4769
4770         buf[pos++] = 0;
4771
4772         return buf;
4773 }
4774
4775 /*
4776  * Repository references
4777  */
4778
4779 static struct ref *refs;
4780 static size_t refs_size;
4781
4782 /* Id <-> ref store */
4783 static struct ref ***id_refs;
4784 static size_t id_refs_size;
4785
4786 static struct ref **
4787 get_refs(char *id)
4788 {
4789         struct ref ***tmp_id_refs;
4790         struct ref **ref_list = NULL;
4791         size_t ref_list_size = 0;
4792         size_t i;
4793
4794         for (i = 0; i < id_refs_size; i++)
4795                 if (!strcmp(id, id_refs[i][0]->id))
4796                         return id_refs[i];
4797
4798         tmp_id_refs = realloc(id_refs, (id_refs_size + 1) * sizeof(*id_refs));
4799         if (!tmp_id_refs)
4800                 return NULL;
4801
4802         id_refs = tmp_id_refs;
4803
4804         for (i = 0; i < refs_size; i++) {
4805                 struct ref **tmp;
4806
4807                 if (strcmp(id, refs[i].id))
4808                         continue;
4809
4810                 tmp = realloc(ref_list, (ref_list_size + 1) * sizeof(*ref_list));
4811                 if (!tmp) {
4812                         if (ref_list)
4813                                 free(ref_list);
4814                         return NULL;
4815                 }
4816
4817                 ref_list = tmp;
4818                 if (ref_list_size > 0)
4819                         ref_list[ref_list_size - 1]->next = 1;
4820                 ref_list[ref_list_size] = &refs[i];
4821
4822                 /* XXX: The properties of the commit chains ensures that we can
4823                  * safely modify the shared ref. The repo references will
4824                  * always be similar for the same id. */
4825                 ref_list[ref_list_size]->next = 0;
4826                 ref_list_size++;
4827         }
4828
4829         if (ref_list)
4830                 id_refs[id_refs_size++] = ref_list;
4831
4832         return ref_list;
4833 }
4834
4835 static int
4836 read_ref(char *id, size_t idlen, char *name, size_t namelen)
4837 {
4838         struct ref *ref;
4839         bool tag = FALSE;
4840         bool remote = FALSE;
4841
4842         if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
4843                 /* Commits referenced by tags has "^{}" appended. */
4844                 if (name[namelen - 1] != '}')
4845                         return OK;
4846
4847                 while (namelen > 0 && name[namelen] != '^')
4848                         namelen--;
4849
4850                 tag = TRUE;
4851                 namelen -= STRING_SIZE("refs/tags/");
4852                 name    += STRING_SIZE("refs/tags/");
4853
4854         } else if (!strncmp(name, "refs/remotes/", STRING_SIZE("refs/remotes/"))) {
4855                 remote = TRUE;
4856                 namelen -= STRING_SIZE("refs/remotes/");
4857                 name    += STRING_SIZE("refs/remotes/");
4858
4859         } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
4860                 namelen -= STRING_SIZE("refs/heads/");
4861                 name    += STRING_SIZE("refs/heads/");
4862
4863         } else if (!strcmp(name, "HEAD")) {
4864                 return OK;
4865         }
4866
4867         refs = realloc(refs, sizeof(*refs) * (refs_size + 1));
4868         if (!refs)
4869                 return ERR;
4870
4871         ref = &refs[refs_size++];
4872         ref->name = malloc(namelen + 1);
4873         if (!ref->name)
4874                 return ERR;
4875
4876         strncpy(ref->name, name, namelen);
4877         ref->name[namelen] = 0;
4878         ref->tag = tag;
4879         ref->remote = remote;
4880         string_copy_rev(ref->id, id);
4881
4882         return OK;
4883 }
4884
4885 static int
4886 load_refs(void)
4887 {
4888         const char *cmd_env = getenv("TIG_LS_REMOTE");
4889         const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
4890
4891         return read_properties(popen(cmd, "r"), "\t", read_ref);
4892 }
4893
4894 static int
4895 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
4896 {
4897         if (!strcmp(name, "i18n.commitencoding"))
4898                 string_ncopy(opt_encoding, value, valuelen);
4899
4900         if (!strcmp(name, "core.editor"))
4901                 string_ncopy(opt_editor, value, valuelen);
4902
4903         return OK;
4904 }
4905
4906 static int
4907 load_repo_config(void)
4908 {
4909         return read_properties(popen(GIT_CONFIG " --list", "r"),
4910                                "=", read_repo_config_option);
4911 }
4912
4913 static int
4914 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
4915 {
4916         if (!opt_git_dir[0]) {
4917                 string_ncopy(opt_git_dir, name, namelen);
4918
4919         } else if (opt_is_inside_work_tree == -1) {
4920                 /* This can be 3 different values depending on the
4921                  * version of git being used. If git-rev-parse does not
4922                  * understand --is-inside-work-tree it will simply echo
4923                  * the option else either "true" or "false" is printed.
4924                  * Default to true for the unknown case. */
4925                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
4926
4927         } else {
4928                 string_ncopy(opt_cdup, name, namelen);
4929         }
4930
4931         return OK;
4932 }
4933
4934 /* XXX: The line outputted by "--show-cdup" can be empty so the option
4935  * must be the last one! */
4936 static int
4937 load_repo_info(void)
4938 {
4939         return read_properties(popen("git rev-parse --git-dir --is-inside-work-tree --show-cdup 2>/dev/null", "r"),
4940                                "=", read_repo_info);
4941 }
4942
4943 static int
4944 read_properties(FILE *pipe, const char *separators,
4945                 int (*read_property)(char *, size_t, char *, size_t))
4946 {
4947         char buffer[BUFSIZ];
4948         char *name;
4949         int state = OK;
4950
4951         if (!pipe)
4952                 return ERR;
4953
4954         while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
4955                 char *value;
4956                 size_t namelen;
4957                 size_t valuelen;
4958
4959                 name = chomp_string(name);
4960                 namelen = strcspn(name, separators);
4961
4962                 if (name[namelen]) {
4963                         name[namelen] = 0;
4964                         value = chomp_string(name + namelen + 1);
4965                         valuelen = strlen(value);
4966
4967                 } else {
4968                         value = "";
4969                         valuelen = 0;
4970                 }
4971
4972                 state = read_property(name, namelen, value, valuelen);
4973         }
4974
4975         if (state != ERR && ferror(pipe))
4976                 state = ERR;
4977
4978         pclose(pipe);
4979
4980         return state;
4981 }
4982
4983
4984 /*
4985  * Main
4986  */
4987
4988 static void __NORETURN
4989 quit(int sig)
4990 {
4991         /* XXX: Restore tty modes and let the OS cleanup the rest! */
4992         if (cursed)
4993                 endwin();
4994         exit(0);
4995 }
4996
4997 static void __NORETURN
4998 die(const char *err, ...)
4999 {
5000         va_list args;
5001
5002         endwin();
5003
5004         va_start(args, err);
5005         fputs("tig: ", stderr);
5006         vfprintf(stderr, err, args);
5007         fputs("\n", stderr);
5008         va_end(args);
5009
5010         exit(1);
5011 }
5012
5013 static void
5014 warn(const char *msg, ...)
5015 {
5016         va_list args;
5017
5018         va_start(args, msg);
5019         fputs("tig warning: ", stderr);
5020         vfprintf(stderr, msg, args);
5021         fputs("\n", stderr);
5022         va_end(args);
5023 }
5024
5025 int
5026 main(int argc, char *argv[])
5027 {
5028         struct view *view;
5029         enum request request;
5030         size_t i;
5031
5032         signal(SIGINT, quit);
5033
5034         if (setlocale(LC_ALL, "")) {
5035                 char *codeset = nl_langinfo(CODESET);
5036
5037                 string_ncopy(opt_codeset, codeset, strlen(codeset));
5038         }
5039
5040         if (load_repo_info() == ERR)
5041                 die("Failed to load repo info.");
5042
5043         if (load_options() == ERR)
5044                 die("Failed to load user config.");
5045
5046         /* Load the repo config file so options can be overwritten from
5047          * the command line. */
5048         if (load_repo_config() == ERR)
5049                 die("Failed to load repo config.");
5050
5051         if (!parse_options(argc, argv))
5052                 return 0;
5053
5054         /* Require a git repository unless when running in pager mode. */
5055         if (!opt_git_dir[0])
5056                 die("Not a git repository");
5057
5058         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
5059                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
5060                 if (opt_iconv == ICONV_NONE)
5061                         die("Failed to initialize character set conversion");
5062         }
5063
5064         if (load_refs() == ERR)
5065                 die("Failed to load refs.");
5066
5067         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
5068                 view->cmd_env = getenv(view->cmd_env);
5069
5070         request = opt_request;
5071
5072         init_display();
5073
5074         while (view_driver(display[current_view], request)) {
5075                 int key;
5076                 int i;
5077
5078                 foreach_view (view, i)
5079                         update_view(view);
5080
5081                 /* Refresh, accept single keystroke of input */
5082                 key = wgetch(status_win);
5083
5084                 /* wgetch() with nodelay() enabled returns ERR when there's no
5085                  * input. */
5086                 if (key == ERR) {
5087                         request = REQ_NONE;
5088                         continue;
5089                 }
5090
5091                 request = get_keybinding(display[current_view]->keymap, key);
5092
5093                 /* Some low-level request handling. This keeps access to
5094                  * status_win restricted. */
5095                 switch (request) {
5096                 case REQ_PROMPT:
5097                 {
5098                         char *cmd = read_prompt(":");
5099
5100                         if (cmd && string_format(opt_cmd, "git %s", cmd)) {
5101                                 if (strncmp(cmd, "show", 4) && isspace(cmd[4])) {
5102                                         opt_request = REQ_VIEW_DIFF;
5103                                 } else {
5104                                         opt_request = REQ_VIEW_PAGER;
5105                                 }
5106                                 break;
5107                         }
5108
5109                         request = REQ_NONE;
5110                         break;
5111                 }
5112                 case REQ_SEARCH:
5113                 case REQ_SEARCH_BACK:
5114                 {
5115                         const char *prompt = request == REQ_SEARCH
5116                                            ? "/" : "?";
5117                         char *search = read_prompt(prompt);
5118
5119                         if (search)
5120                                 string_ncopy(opt_search, search, strlen(search));
5121                         else
5122                                 request = REQ_NONE;
5123                         break;
5124                 }
5125                 case REQ_SCREEN_RESIZE:
5126                 {
5127                         int height, width;
5128
5129                         getmaxyx(stdscr, height, width);
5130
5131                         /* Resize the status view and let the view driver take
5132                          * care of resizing the displayed views. */
5133                         wresize(status_win, 1, width);
5134                         mvwin(status_win, height - 1, 0);
5135                         wrefresh(status_win);
5136                         break;
5137                 }
5138                 default:
5139                         break;
5140                 }
5141         }
5142
5143         quit(0);
5144
5145         return 0;
5146 }