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