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