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