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