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