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