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