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