Minor tidyup
[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.3"
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 <curses.h>
34
35 static void die(const char *err, ...);
36 static void report(const char *msg, ...);
37 static int read_properties(FILE *pipe, const char *separators, int (*read)(char *, int, char *, int));
38 static void set_nonblocking_input(bool loading);
39 static size_t utf8_length(const char *string, size_t max_width, int *coloffset, int *trimmed);
40 static void load_help_page(void);
41
42 #define ABS(x)          ((x) >= 0  ? (x) : -(x))
43 #define MIN(x, y)       ((x) < (y) ? (x) :  (y))
44
45 #define ARRAY_SIZE(x)   (sizeof(x) / sizeof(x[0]))
46 #define STRING_SIZE(x)  (sizeof(x) - 1)
47
48 #define SIZEOF_REF      256     /* Size of symbolic or SHA1 ID. */
49 #define SIZEOF_CMD      1024    /* Size of command buffer. */
50
51 /* This color name can be used to refer to the default term colors. */
52 #define COLOR_DEFAULT   (-1)
53
54 /* The format and size of the date column in the main view. */
55 #define DATE_FORMAT     "%Y-%m-%d %H:%M"
56 #define DATE_COLS       STRING_SIZE("2006-04-29 14:21 ")
57
58 #define AUTHOR_COLS     20
59
60 /* The default interval between line numbers. */
61 #define NUMBER_INTERVAL 1
62
63 #define TABSIZE         8
64
65 #define SCALE_SPLIT_VIEW(height)        ((height) * 2 / 3)
66
67 #define TIG_LS_REMOTE \
68         "git ls-remote . 2>/dev/null"
69
70 #define TIG_DIFF_CMD \
71         "git show --patch-with-stat --find-copies-harder -B -C %s"
72
73 #define TIG_LOG_CMD     \
74         "git log --cc --stat -n100 %s"
75
76 #define TIG_MAIN_CMD \
77         "git log --topo-order --stat --pretty=raw %s"
78
79 /* XXX: Needs to be defined to the empty string. */
80 #define TIG_HELP_CMD    ""
81 #define TIG_PAGER_CMD   ""
82
83 /* Some ascii-shorthands fitted into the ncurses namespace. */
84 #define KEY_TAB         '\t'
85 #define KEY_RETURN      '\r'
86 #define KEY_ESC         27
87
88
89 struct ref {
90         char *name;             /* Ref name; tag or head names are shortened. */
91         char id[41];            /* Commit SHA1 ID */
92         unsigned int tag:1;     /* Is it a tag? */
93         unsigned int next:1;    /* For ref lists: are there more refs? */
94 };
95
96 static struct ref **get_refs(char *id);
97
98 struct int_map {
99         const char *name;
100         int namelen;
101         int value;
102 };
103
104 static int
105 set_from_int_map(struct int_map *map, size_t map_size,
106                  int *value, const char *name, int namelen)
107 {
108
109         int i;
110
111         for (i = 0; i < map_size; i++)
112                 if (namelen == map[i].namelen &&
113                     !strncasecmp(name, map[i].name, namelen)) {
114                         *value = map[i].value;
115                         return OK;
116                 }
117
118         return ERR;
119 }
120
121
122 /*
123  * String helpers
124  */
125
126 static inline void
127 string_ncopy(char *dst, const char *src, int dstlen)
128 {
129         strncpy(dst, src, dstlen - 1);
130         dst[dstlen - 1] = 0;
131
132 }
133
134 /* Shorthand for safely copying into a fixed buffer. */
135 #define string_copy(dst, src) \
136         string_ncopy(dst, src, sizeof(dst))
137
138 static char *
139 chomp_string(char *name)
140 {
141         int namelen;
142
143         while (isspace(*name))
144                 name++;
145
146         namelen = strlen(name) - 1;
147         while (namelen > 0 && isspace(name[namelen]))
148                 name[namelen--] = 0;
149
150         return name;
151 }
152
153 static bool
154 string_nformat(char *buf, size_t bufsize, int *bufpos, const char *fmt, ...)
155 {
156         va_list args;
157         int pos = bufpos ? *bufpos : 0;
158
159         va_start(args, fmt);
160         pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
161         va_end(args);
162
163         if (bufpos)
164                 *bufpos = pos;
165
166         return pos >= bufsize ? FALSE : TRUE;
167 }
168
169 #define string_format(buf, fmt, args...) \
170         string_nformat(buf, sizeof(buf), NULL, fmt, args)
171
172 #define string_format_from(buf, from, fmt, args...) \
173         string_nformat(buf, sizeof(buf), from, fmt, args)
174
175 /* Shell quoting
176  *
177  * NOTE: The following is a slightly modified copy of the git project's shell
178  * quoting routines found in the quote.c file.
179  *
180  * Help to copy the thing properly quoted for the shell safety.  any single
181  * quote is replaced with '\'', any exclamation point is replaced with '\!',
182  * and the whole thing is enclosed in a
183  *
184  * E.g.
185  *  original     sq_quote     result
186  *  name     ==> name      ==> 'name'
187  *  a b      ==> a b       ==> 'a b'
188  *  a'b      ==> a'\''b    ==> 'a'\''b'
189  *  a!b      ==> a'\!'b    ==> 'a'\!'b'
190  */
191
192 static size_t
193 sq_quote(char buf[SIZEOF_CMD], size_t bufsize, const char *src)
194 {
195         char c;
196
197 #define BUFPUT(x) do { if (bufsize < SIZEOF_CMD) buf[bufsize++] = (x); } while (0)
198
199         BUFPUT('\'');
200         while ((c = *src++)) {
201                 if (c == '\'' || c == '!') {
202                         BUFPUT('\'');
203                         BUFPUT('\\');
204                         BUFPUT(c);
205                         BUFPUT('\'');
206                 } else {
207                         BUFPUT(c);
208                 }
209         }
210         BUFPUT('\'');
211
212         return bufsize;
213 }
214
215
216 /*
217  * User requests
218  */
219
220 #define REQ_INFO \
221         /* XXX: Keep the view request first and in sync with views[]. */ \
222         REQ_GROUP("View switching") \
223         REQ_(VIEW_MAIN,         "Show main view"), \
224         REQ_(VIEW_DIFF,         "Show diff view"), \
225         REQ_(VIEW_LOG,          "Show log view"), \
226         REQ_(VIEW_HELP,         "Show help page"), \
227         REQ_(VIEW_PAGER,        "Show pager view"), \
228         \
229         REQ_GROUP("View manipulation") \
230         REQ_(ENTER,             "Enter current line and scroll"), \
231         REQ_(NEXT,              "Move to next"), \
232         REQ_(PREVIOUS,          "Move to previous"), \
233         REQ_(VIEW_NEXT,         "Move focus to next view"), \
234         REQ_(VIEW_CLOSE,        "Close the current view"), \
235         REQ_(QUIT,              "Close all views and quit"), \
236         \
237         REQ_GROUP("Cursor navigation") \
238         REQ_(MOVE_UP,           "Move cursor one line up"), \
239         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
240         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
241         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
242         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
243         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
244         \
245         REQ_GROUP("Scrolling") \
246         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
247         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
248         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
249         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
250         \
251         REQ_GROUP("Misc") \
252         REQ_(PROMPT,            "Bring up the prompt"), \
253         REQ_(SCREEN_UPDATE,     "Update the screen"), \
254         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
255         REQ_(SCREEN_RESIZE,     "Resize the screen"), \
256         REQ_(SHOW_VERSION,      "Show version information"), \
257         REQ_(STOP_LOADING,      "Stop all loading views"), \
258         REQ_(TOGGLE_LINENO,     "Toggle line numbers"),
259
260
261 /* User action requests. */
262 enum request {
263 #define REQ_GROUP(help)
264 #define REQ_(req, help) REQ_##req
265
266         /* Offset all requests to avoid conflicts with ncurses getch values. */
267         REQ_OFFSET = KEY_MAX + 1,
268         REQ_INFO
269
270 #undef  REQ_GROUP
271 #undef  REQ_
272 };
273
274 struct request_info {
275         enum request request;
276         char *help;
277 };
278
279 static struct request_info req_info[] = {
280 #define REQ_GROUP(help) { 0, (help) },
281 #define REQ_(req, help) { REQ_##req, (help) }
282         REQ_INFO
283 #undef  REQ_GROUP
284 #undef  REQ_
285 };
286
287 /*
288  * Options
289  */
290
291 static const char usage[] =
292 VERSION " (" __DATE__ ")\n"
293 "\n"
294 "Usage: tig [options]\n"
295 "   or: tig [options] [--] [git log options]\n"
296 "   or: tig [options] log  [git log options]\n"
297 "   or: tig [options] diff [git diff options]\n"
298 "   or: tig [options] show [git show options]\n"
299 "   or: tig [options] <    [git command output]\n"
300 "\n"
301 "Options:\n"
302 "  -l                          Start up in log view\n"
303 "  -d                          Start up in diff view\n"
304 "  -n[I], --line-number[=I]    Show line numbers with given interval\n"
305 "  -b[N], --tab-size[=N]       Set number of spaces for tab expansion\n"
306 "  --                          Mark end of tig options\n"
307 "  -v, --version               Show version and exit\n"
308 "  -h, --help                  Show help message and exit\n";
309
310 /* Option and state variables. */
311 static bool opt_line_number     = FALSE;
312 static int opt_num_interval     = NUMBER_INTERVAL;
313 static int opt_tab_size         = TABSIZE;
314 static enum request opt_request = REQ_VIEW_MAIN;
315 static char opt_cmd[SIZEOF_CMD] = "";
316 static char opt_encoding[20]    = "";
317 static bool opt_utf8            = TRUE;
318 static FILE *opt_pipe           = NULL;
319
320 enum option_type {
321         OPT_NONE,
322         OPT_INT,
323 };
324
325 static bool
326 check_option(char *opt, char short_name, char *name, enum option_type type, ...)
327 {
328         va_list args;
329         char *value = "";
330         int *number;
331
332         if (opt[0] != '-')
333                 return FALSE;
334
335         if (opt[1] == '-') {
336                 int namelen = strlen(name);
337
338                 opt += 2;
339
340                 if (strncmp(opt, name, namelen))
341                         return FALSE;
342
343                 if (opt[namelen] == '=')
344                         value = opt + namelen + 1;
345
346         } else {
347                 if (!short_name || opt[1] != short_name)
348                         return FALSE;
349                 value = opt + 2;
350         }
351
352         va_start(args, type);
353         if (type == OPT_INT) {
354                 number = va_arg(args, int *);
355                 if (isdigit(*value))
356                         *number = atoi(value);
357         }
358         va_end(args);
359
360         return TRUE;
361 }
362
363 /* Returns the index of log or diff command or -1 to exit. */
364 static bool
365 parse_options(int argc, char *argv[])
366 {
367         int i;
368
369         for (i = 1; i < argc; i++) {
370                 char *opt = argv[i];
371
372                 if (!strcmp(opt, "-l")) {
373                         opt_request = REQ_VIEW_LOG;
374                         continue;
375                 }
376
377                 if (!strcmp(opt, "-d")) {
378                         opt_request = REQ_VIEW_DIFF;
379                         continue;
380                 }
381
382                 if (check_option(opt, 'n', "line-number", OPT_INT, &opt_num_interval)) {
383                         opt_line_number = TRUE;
384                         continue;
385                 }
386
387                 if (check_option(opt, 'b', "tab-size", OPT_INT, &opt_tab_size)) {
388                         opt_tab_size = MIN(opt_tab_size, TABSIZE);
389                         continue;
390                 }
391
392                 if (check_option(opt, 'v', "version", OPT_NONE)) {
393                         printf("tig version %s\n", VERSION);
394                         return FALSE;
395                 }
396
397                 if (check_option(opt, 'h', "help", OPT_NONE)) {
398                         printf(usage);
399                         return FALSE;
400                 }
401
402                 if (!strcmp(opt, "--")) {
403                         i++;
404                         break;
405                 }
406
407                 if (!strcmp(opt, "log") ||
408                     !strcmp(opt, "diff") ||
409                     !strcmp(opt, "show")) {
410                         opt_request = opt[0] == 'l'
411                                     ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
412                         break;
413                 }
414
415                 if (opt[0] && opt[0] != '-')
416                         break;
417
418                 die("unknown command '%s'", opt);
419         }
420
421         if (!isatty(STDIN_FILENO)) {
422                 opt_request = REQ_VIEW_PAGER;
423                 opt_pipe = stdin;
424
425         } else if (i < argc) {
426                 size_t buf_size;
427
428                 if (opt_request == REQ_VIEW_MAIN)
429                         /* XXX: This is vulnerable to the user overriding
430                          * options required for the main view parser. */
431                         string_copy(opt_cmd, "git log --stat --pretty=raw");
432                 else
433                         string_copy(opt_cmd, "git");
434                 buf_size = strlen(opt_cmd);
435
436                 while (buf_size < sizeof(opt_cmd) && i < argc) {
437                         opt_cmd[buf_size++] = ' ';
438                         buf_size = sq_quote(opt_cmd, buf_size, argv[i++]);
439                 }
440
441                 if (buf_size >= sizeof(opt_cmd))
442                         die("command too long");
443
444                 opt_cmd[buf_size] = 0;
445
446         }
447
448         if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
449                 opt_utf8 = FALSE;
450
451         return TRUE;
452 }
453
454
455 static struct int_map color_map[] = {
456 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
457         COLOR_MAP(DEFAULT),
458         COLOR_MAP(BLACK),
459         COLOR_MAP(BLUE),
460         COLOR_MAP(CYAN),
461         COLOR_MAP(GREEN),
462         COLOR_MAP(MAGENTA),
463         COLOR_MAP(RED),
464         COLOR_MAP(WHITE),
465         COLOR_MAP(YELLOW),
466 };
467
468 static struct int_map attr_map[] = {
469 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
470         ATTR_MAP(NORMAL),
471         ATTR_MAP(BLINK),
472         ATTR_MAP(BOLD),
473         ATTR_MAP(DIM),
474         ATTR_MAP(REVERSE),
475         ATTR_MAP(STANDOUT),
476         ATTR_MAP(UNDERLINE),
477 };
478
479 #define LINE_INFO \
480 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
481 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
482 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
483 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
484 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
485 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
486 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
487 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
488 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
489 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
490 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
491 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
492 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
493 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
494 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
495 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
496 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
497 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
498 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
499 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
500 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
501 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
502 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
503 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
504 LINE(AUTHOR,       "author ",           COLOR_CYAN,     COLOR_DEFAULT,  0), \
505 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
506 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
507 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
508 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
509 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
510 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
511 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
512 LINE(MAIN_DATE,    "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
513 LINE(MAIN_AUTHOR,  "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
514 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
515 LINE(MAIN_DELIM,   "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
516 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
517 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
518
519
520 /*
521  * Line-oriented content detection.
522  */
523
524 enum line_type {
525 #define LINE(type, line, fg, bg, attr) \
526         LINE_##type
527         LINE_INFO
528 #undef  LINE
529 };
530
531 struct line_info {
532         const char *name;       /* Option name. */
533         int namelen;            /* Size of option name. */
534         const char *line;       /* The start of line to match. */
535         int linelen;            /* Size of string to match. */
536         int fg, bg, attr;       /* Color and text attributes for the lines. */
537 };
538
539 static struct line_info line_info[] = {
540 #define LINE(type, line, fg, bg, attr) \
541         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
542         LINE_INFO
543 #undef  LINE
544 };
545
546 static enum line_type
547 get_line_type(char *line)
548 {
549         int linelen = strlen(line);
550         enum line_type type;
551
552         for (type = 0; type < ARRAY_SIZE(line_info); type++)
553                 /* Case insensitive search matches Signed-off-by lines better. */
554                 if (linelen >= line_info[type].linelen &&
555                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
556                         return type;
557
558         return LINE_DEFAULT;
559 }
560
561 static inline int
562 get_line_attr(enum line_type type)
563 {
564         assert(type < ARRAY_SIZE(line_info));
565         return COLOR_PAIR(type) | line_info[type].attr;
566 }
567
568 static struct line_info *
569 get_line_info(char *name, int namelen)
570 {
571         enum line_type type;
572         int i;
573
574         /* Diff-Header -> DIFF_HEADER */
575         for (i = 0; i < namelen; i++) {
576                 if (name[i] == '-')
577                         name[i] = '_';
578                 else if (name[i] == '.')
579                         name[i] = '_';
580         }
581
582         for (type = 0; type < ARRAY_SIZE(line_info); type++)
583                 if (namelen == line_info[type].namelen &&
584                     !strncasecmp(line_info[type].name, name, namelen))
585                         return &line_info[type];
586
587         return NULL;
588 }
589
590 static void
591 init_colors(void)
592 {
593         int default_bg = COLOR_BLACK;
594         int default_fg = COLOR_WHITE;
595         enum line_type type;
596
597         start_color();
598
599         if (use_default_colors() != ERR) {
600                 default_bg = -1;
601                 default_fg = -1;
602         }
603
604         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
605                 struct line_info *info = &line_info[type];
606                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
607                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
608
609                 init_pair(type, fg, bg);
610         }
611 }
612
613 struct line {
614         enum line_type type;
615         void *data;             /* User data */
616 };
617
618
619 /*
620  * User config file handling.
621  */
622
623 #define set_color(color, name, namelen) \
624         set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, namelen)
625
626 #define set_attribute(attr, name, namelen) \
627         set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, namelen)
628
629 static int   config_lineno;
630 static bool  config_errors;
631 static char *config_msg;
632
633 static int
634 set_option(char *opt, int optlen, char *value, int valuelen)
635 {
636         /* Reads: "color" object fgcolor bgcolor [attr] */
637         if (!strcmp(opt, "color")) {
638                 struct line_info *info;
639
640                 value = chomp_string(value);
641                 valuelen = strcspn(value, " \t");
642                 info = get_line_info(value, valuelen);
643                 if (!info) {
644                         config_msg = "Unknown color name";
645                         return ERR;
646                 }
647
648                 value = chomp_string(value + valuelen);
649                 valuelen = strcspn(value, " \t");
650                 if (set_color(&info->fg, value, valuelen) == ERR) {
651                         config_msg = "Unknown color";
652                         return ERR;
653                 }
654
655                 value = chomp_string(value + valuelen);
656                 valuelen = strcspn(value, " \t");
657                 if (set_color(&info->bg, value, valuelen) == ERR) {
658                         config_msg = "Unknown color";
659                         return ERR;
660                 }
661
662                 value = chomp_string(value + valuelen);
663                 if (*value &&
664                     set_attribute(&info->attr, value, strlen(value)) == ERR) {
665                         config_msg = "Unknown attribute";
666                         return ERR;
667                 }
668
669                 return OK;
670         }
671
672         return ERR;
673 }
674
675 static int
676 read_option(char *opt, int optlen, char *value, int valuelen)
677 {
678         config_lineno++;
679         config_msg = "Internal error";
680
681         optlen = strcspn(opt, "#;");
682         if (optlen == 0) {
683                 /* The whole line is a commend or empty. */
684                 return OK;
685
686         } else if (opt[optlen] != 0) {
687                 /* Part of the option name is a comment, so the value part
688                  * should be ignored. */
689                 valuelen = 0;
690                 opt[optlen] = value[valuelen] = 0;
691         } else {
692                 /* Else look for comment endings in the value. */
693                 valuelen = strcspn(value, "#;");
694                 value[valuelen] = 0;
695         }
696
697         if (set_option(opt, optlen, value, valuelen) == ERR) {
698                 fprintf(stderr, "Error on line %d, near '%.*s' option: %s\n",
699                         config_lineno, optlen, opt, config_msg);
700                 config_errors = TRUE;
701         }
702
703         /* Always keep going if errors are encountered. */
704         return OK;
705 }
706
707 static int
708 load_options(void)
709 {
710         char *home = getenv("HOME");
711         char buf[1024];
712         FILE *file;
713
714         config_lineno = 0;
715         config_errors = FALSE;
716
717         if (!home || !string_format(buf, "%s/.tigrc", home))
718                 return ERR;
719
720         /* It's ok that the file doesn't exist. */
721         file = fopen(buf, "r");
722         if (!file)
723                 return OK;
724
725         if (read_properties(file, " \t", read_option) == ERR ||
726             config_errors == TRUE)
727                 fprintf(stderr, "Errors while loading %s.\n", buf);
728
729         return OK;
730 }
731
732
733 /*
734  * The viewer
735  */
736
737 struct view;
738 struct view_ops;
739
740 /* The display array of active views and the index of the current view. */
741 static struct view *display[2];
742 static unsigned int current_view;
743
744 #define foreach_view(view, i) \
745         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
746
747 #define displayed_views()       (display[1] != NULL ? 2 : 1)
748
749 /* Current head and commit ID */
750 static char ref_commit[SIZEOF_REF]      = "HEAD";
751 static char ref_head[SIZEOF_REF]        = "HEAD";
752
753 struct view {
754         const char *name;       /* View name */
755         const char *cmd_fmt;    /* Default command line format */
756         const char *cmd_env;    /* Command line set via environment */
757         const char *id;         /* Points to either of ref_{head,commit} */
758
759         struct view_ops *ops;   /* View operations */
760
761         char cmd[SIZEOF_CMD];   /* Command buffer */
762         char ref[SIZEOF_REF];   /* Hovered commit reference */
763         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
764
765         int height, width;      /* The width and height of the main window */
766         WINDOW *win;            /* The main window */
767         WINDOW *title;          /* The title window living below the main window */
768
769         /* Navigation */
770         unsigned long offset;   /* Offset of the window top */
771         unsigned long lineno;   /* Current line number */
772
773         /* If non-NULL, points to the view that opened this view. If this view
774          * is closed tig will switch back to the parent view. */
775         struct view *parent;
776
777         /* Buffering */
778         unsigned long lines;    /* Total number of lines */
779         struct line *line;      /* Line index */
780         unsigned long line_size;/* Total number of allocated lines */
781         unsigned int digits;    /* Number of digits in the lines member. */
782
783         /* Loading */
784         FILE *pipe;
785         time_t start_time;
786 };
787
788 struct view_ops {
789         /* What type of content being displayed. Used in the title bar. */
790         const char *type;
791         /* Draw one line; @lineno must be < view->height. */
792         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
793         /* Read one line; updates view->line. */
794         bool (*read)(struct view *view, char *data);
795         /* Depending on view, change display based on current line. */
796         bool (*enter)(struct view *view, struct line *line);
797 };
798
799 static struct view_ops pager_ops;
800 static struct view_ops main_ops;
801
802 #define VIEW_STR(name, cmd, env, ref, ops) \
803         { name, cmd, #env, ref, ops }
804
805 #define VIEW_(id, name, ops, ref) \
806         VIEW_STR(name, TIG_##id##_CMD,  TIG_##id##_CMD, ref, ops)
807
808
809 static struct view views[] = {
810         VIEW_(MAIN,  "main",  &main_ops,  ref_head),
811         VIEW_(DIFF,  "diff",  &pager_ops, ref_commit),
812         VIEW_(LOG,   "log",   &pager_ops, ref_head),
813         VIEW_(HELP,  "help",  &pager_ops, "static"),
814         VIEW_(PAGER, "pager", &pager_ops, "static"),
815 };
816
817 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
818
819
820 static bool
821 draw_view_line(struct view *view, unsigned int lineno)
822 {
823         if (view->offset + lineno >= view->lines)
824                 return FALSE;
825
826         return view->ops->draw(view, &view->line[view->offset + lineno], lineno);
827 }
828
829 static void
830 redraw_view_from(struct view *view, int lineno)
831 {
832         assert(0 <= lineno && lineno < view->height);
833
834         for (; lineno < view->height; lineno++) {
835                 if (!draw_view_line(view, lineno))
836                         break;
837         }
838
839         redrawwin(view->win);
840         wrefresh(view->win);
841 }
842
843 static void
844 redraw_view(struct view *view)
845 {
846         wclear(view->win);
847         redraw_view_from(view, 0);
848 }
849
850
851 static void
852 update_view_title(struct view *view)
853 {
854         if (view == display[current_view])
855                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
856         else
857                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
858
859         werase(view->title);
860         wmove(view->title, 0, 0);
861
862         if (*view->ref)
863                 wprintw(view->title, "[%s] %s", view->name, view->ref);
864         else
865                 wprintw(view->title, "[%s]", view->name);
866
867         if (view->lines || view->pipe) {
868                 unsigned int lines = view->lines
869                                    ? (view->lineno + 1) * 100 / view->lines
870                                    : 0;
871
872                 wprintw(view->title, " - %s %d of %d (%d%%)",
873                         view->ops->type,
874                         view->lineno + 1,
875                         view->lines,
876                         lines);
877         }
878
879         if (view->pipe) {
880                 time_t secs = time(NULL) - view->start_time;
881
882                 /* Three git seconds are a long time ... */
883                 if (secs > 2)
884                         wprintw(view->title, " %lds", secs);
885         }
886
887         wmove(view->title, 0, view->width - 1);
888         wrefresh(view->title);
889 }
890
891 static void
892 resize_display(void)
893 {
894         int offset, i;
895         struct view *base = display[0];
896         struct view *view = display[1] ? display[1] : display[0];
897
898         /* Setup window dimensions */
899
900         getmaxyx(stdscr, base->height, base->width);
901
902         /* Make room for the status window. */
903         base->height -= 1;
904
905         if (view != base) {
906                 /* Horizontal split. */
907                 view->width   = base->width;
908                 view->height  = SCALE_SPLIT_VIEW(base->height);
909                 base->height -= view->height;
910
911                 /* Make room for the title bar. */
912                 view->height -= 1;
913         }
914
915         /* Make room for the title bar. */
916         base->height -= 1;
917
918         offset = 0;
919
920         foreach_view (view, i) {
921                 if (!view->win) {
922                         view->win = newwin(view->height, 0, offset, 0);
923                         if (!view->win)
924                                 die("Failed to create %s view", view->name);
925
926                         scrollok(view->win, TRUE);
927
928                         view->title = newwin(1, 0, offset + view->height, 0);
929                         if (!view->title)
930                                 die("Failed to create title window");
931
932                 } else {
933                         wresize(view->win, view->height, view->width);
934                         mvwin(view->win,   offset, 0);
935                         mvwin(view->title, offset + view->height, 0);
936                 }
937
938                 offset += view->height + 1;
939         }
940 }
941
942 static void
943 redraw_display(void)
944 {
945         struct view *view;
946         int i;
947
948         foreach_view (view, i) {
949                 redraw_view(view);
950                 update_view_title(view);
951         }
952 }
953
954 static void
955 update_display_cursor(void)
956 {
957         struct view *view = display[current_view];
958
959         /* Move the cursor to the right-most column of the cursor line.
960          *
961          * XXX: This could turn out to be a bit expensive, but it ensures that
962          * the cursor does not jump around. */
963         if (view->lines) {
964                 wmove(view->win, view->lineno - view->offset, view->width - 1);
965                 wrefresh(view->win);
966         }
967 }
968
969 /*
970  * Navigation
971  */
972
973 /* Scrolling backend */
974 static void
975 do_scroll_view(struct view *view, int lines, bool redraw)
976 {
977         /* The rendering expects the new offset. */
978         view->offset += lines;
979
980         assert(0 <= view->offset && view->offset < view->lines);
981         assert(lines);
982
983         /* Redraw the whole screen if scrolling is pointless. */
984         if (view->height < ABS(lines)) {
985                 redraw_view(view);
986
987         } else {
988                 int line = lines > 0 ? view->height - lines : 0;
989                 int end = line + ABS(lines);
990
991                 wscrl(view->win, lines);
992
993                 for (; line < end; line++) {
994                         if (!draw_view_line(view, line))
995                                 break;
996                 }
997         }
998
999         /* Move current line into the view. */
1000         if (view->lineno < view->offset) {
1001                 view->lineno = view->offset;
1002                 draw_view_line(view, 0);
1003
1004         } else if (view->lineno >= view->offset + view->height) {
1005                 if (view->lineno == view->offset + view->height) {
1006                         /* Clear the hidden line so it doesn't show if the view
1007                          * is scrolled up. */
1008                         wmove(view->win, view->height, 0);
1009                         wclrtoeol(view->win);
1010                 }
1011                 view->lineno = view->offset + view->height - 1;
1012                 draw_view_line(view, view->lineno - view->offset);
1013         }
1014
1015         assert(view->offset <= view->lineno && view->lineno < view->lines);
1016
1017         if (!redraw)
1018                 return;
1019
1020         redrawwin(view->win);
1021         wrefresh(view->win);
1022         report("");
1023 }
1024
1025 /* Scroll frontend */
1026 static void
1027 scroll_view(struct view *view, enum request request)
1028 {
1029         int lines = 1;
1030
1031         switch (request) {
1032         case REQ_SCROLL_PAGE_DOWN:
1033                 lines = view->height;
1034         case REQ_SCROLL_LINE_DOWN:
1035                 if (view->offset + lines > view->lines)
1036                         lines = view->lines - view->offset;
1037
1038                 if (lines == 0 || view->offset + view->height >= view->lines) {
1039                         report("Cannot scroll beyond the last line");
1040                         return;
1041                 }
1042                 break;
1043
1044         case REQ_SCROLL_PAGE_UP:
1045                 lines = view->height;
1046         case REQ_SCROLL_LINE_UP:
1047                 if (lines > view->offset)
1048                         lines = view->offset;
1049
1050                 if (lines == 0) {
1051                         report("Cannot scroll beyond the first line");
1052                         return;
1053                 }
1054
1055                 lines = -lines;
1056                 break;
1057
1058         default:
1059                 die("request %d not handled in switch", request);
1060         }
1061
1062         do_scroll_view(view, lines, TRUE);
1063 }
1064
1065 /* Cursor moving */
1066 static void
1067 move_view(struct view *view, enum request request, bool redraw)
1068 {
1069         int steps;
1070
1071         switch (request) {
1072         case REQ_MOVE_FIRST_LINE:
1073                 steps = -view->lineno;
1074                 break;
1075
1076         case REQ_MOVE_LAST_LINE:
1077                 steps = view->lines - view->lineno - 1;
1078                 break;
1079
1080         case REQ_MOVE_PAGE_UP:
1081                 steps = view->height > view->lineno
1082                       ? -view->lineno : -view->height;
1083                 break;
1084
1085         case REQ_MOVE_PAGE_DOWN:
1086                 steps = view->lineno + view->height >= view->lines
1087                       ? view->lines - view->lineno - 1 : view->height;
1088                 break;
1089
1090         case REQ_MOVE_UP:
1091                 steps = -1;
1092                 break;
1093
1094         case REQ_MOVE_DOWN:
1095                 steps = 1;
1096                 break;
1097
1098         default:
1099                 die("request %d not handled in switch", request);
1100         }
1101
1102         if (steps <= 0 && view->lineno == 0) {
1103                 report("Cannot move beyond the first line");
1104                 return;
1105
1106         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
1107                 report("Cannot move beyond the last line");
1108                 return;
1109         }
1110
1111         /* Move the current line */
1112         view->lineno += steps;
1113         assert(0 <= view->lineno && view->lineno < view->lines);
1114
1115         /* Repaint the old "current" line if we be scrolling */
1116         if (ABS(steps) < view->height) {
1117                 int prev_lineno = view->lineno - steps - view->offset;
1118
1119                 wmove(view->win, prev_lineno, 0);
1120                 wclrtoeol(view->win);
1121                 draw_view_line(view,  prev_lineno);
1122         }
1123
1124         /* Check whether the view needs to be scrolled */
1125         if (view->lineno < view->offset ||
1126             view->lineno >= view->offset + view->height) {
1127                 if (steps < 0 && -steps > view->offset) {
1128                         steps = -view->offset;
1129
1130                 } else if (steps > 0) {
1131                         if (view->lineno == view->lines - 1 &&
1132                             view->lines > view->height) {
1133                                 steps = view->lines - view->offset - 1;
1134                                 if (steps >= view->height)
1135                                         steps -= view->height - 1;
1136                         }
1137                 }
1138
1139                 do_scroll_view(view, steps, redraw);
1140                 return;
1141         }
1142
1143         /* Draw the current line */
1144         draw_view_line(view, view->lineno - view->offset);
1145
1146         if (!redraw)
1147                 return;
1148
1149         redrawwin(view->win);
1150         wrefresh(view->win);
1151         report("");
1152 }
1153
1154
1155 /*
1156  * Incremental updating
1157  */
1158
1159 static void
1160 end_update(struct view *view)
1161 {
1162         if (!view->pipe)
1163                 return;
1164         set_nonblocking_input(FALSE);
1165         if (view->pipe == stdin)
1166                 fclose(view->pipe);
1167         else
1168                 pclose(view->pipe);
1169         view->pipe = NULL;
1170 }
1171
1172 static bool
1173 begin_update(struct view *view)
1174 {
1175         const char *id = view->id;
1176
1177         if (view->pipe)
1178                 end_update(view);
1179
1180         if (opt_cmd[0]) {
1181                 string_copy(view->cmd, opt_cmd);
1182                 opt_cmd[0] = 0;
1183                 /* When running random commands, the view ref could have become
1184                  * invalid so clear it. */
1185                 view->ref[0] = 0;
1186         } else {
1187                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
1188
1189                 if (!string_format(view->cmd, format, id, id, id, id, id))
1190                         return FALSE;
1191         }
1192
1193         /* Special case for the pager view. */
1194         if (opt_pipe) {
1195                 view->pipe = opt_pipe;
1196                 opt_pipe = NULL;
1197         } else {
1198                 view->pipe = popen(view->cmd, "r");
1199         }
1200
1201         if (!view->pipe)
1202                 return FALSE;
1203
1204         set_nonblocking_input(TRUE);
1205
1206         view->offset = 0;
1207         view->lines  = 0;
1208         view->lineno = 0;
1209         string_copy(view->vid, id);
1210
1211         if (view->line) {
1212                 int i;
1213
1214                 for (i = 0; i < view->lines; i++)
1215                         if (view->line[i].data)
1216                                 free(view->line[i].data);
1217
1218                 free(view->line);
1219                 view->line = NULL;
1220         }
1221
1222         view->start_time = time(NULL);
1223
1224         return TRUE;
1225 }
1226
1227 static struct line *
1228 realloc_lines(struct view *view, size_t line_size)
1229 {
1230         struct line *tmp = realloc(view->line, sizeof(*view->line) * line_size);
1231
1232         if (!tmp)
1233                 return NULL;
1234
1235         view->line = tmp;
1236         view->line_size = line_size;
1237         return view->line;
1238 }
1239
1240 static bool
1241 update_view(struct view *view)
1242 {
1243         char buffer[BUFSIZ];
1244         char *line;
1245         /* The number of lines to read. If too low it will cause too much
1246          * redrawing (and possible flickering), if too high responsiveness
1247          * will suffer. */
1248         unsigned long lines = view->height;
1249         int redraw_from = -1;
1250
1251         if (!view->pipe)
1252                 return TRUE;
1253
1254         /* Only redraw if lines are visible. */
1255         if (view->offset + view->height >= view->lines)
1256                 redraw_from = view->lines - view->offset;
1257
1258         if (!realloc_lines(view, view->lines + lines))
1259                 goto alloc_error;
1260
1261         while ((line = fgets(buffer, sizeof(buffer), view->pipe))) {
1262                 int linelen = strlen(line);
1263
1264                 if (linelen)
1265                         line[linelen - 1] = 0;
1266
1267                 if (!view->ops->read(view, line))
1268                         goto alloc_error;
1269
1270                 if (lines-- == 1)
1271                         break;
1272         }
1273
1274         {
1275                 int digits;
1276
1277                 lines = view->lines;
1278                 for (digits = 0; lines; digits++)
1279                         lines /= 10;
1280
1281                 /* Keep the displayed view in sync with line number scaling. */
1282                 if (digits != view->digits) {
1283                         view->digits = digits;
1284                         redraw_from = 0;
1285                 }
1286         }
1287
1288         if (redraw_from >= 0) {
1289                 /* If this is an incremental update, redraw the previous line
1290                  * since for commits some members could have changed when
1291                  * loading the main view. */
1292                 if (redraw_from > 0)
1293                         redraw_from--;
1294
1295                 /* Incrementally draw avoids flickering. */
1296                 redraw_view_from(view, redraw_from);
1297         }
1298
1299         /* Update the title _after_ the redraw so that if the redraw picks up a
1300          * commit reference in view->ref it'll be available here. */
1301         update_view_title(view);
1302
1303         if (ferror(view->pipe)) {
1304                 report("Failed to read: %s", strerror(errno));
1305                 goto end;
1306
1307         } else if (feof(view->pipe)) {
1308                 report("");
1309                 goto end;
1310         }
1311
1312         return TRUE;
1313
1314 alloc_error:
1315         report("Allocation failure");
1316
1317 end:
1318         end_update(view);
1319         return FALSE;
1320 }
1321
1322 enum open_flags {
1323         OPEN_DEFAULT = 0,       /* Use default view switching. */
1324         OPEN_SPLIT = 1,         /* Split current view. */
1325         OPEN_BACKGROUNDED = 2,  /* Backgrounded. */
1326         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
1327 };
1328
1329 static void
1330 open_view(struct view *prev, enum request request, enum open_flags flags)
1331 {
1332         bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
1333         bool split = !!(flags & OPEN_SPLIT);
1334         bool reload = !!(flags & OPEN_RELOAD);
1335         struct view *view = VIEW(request);
1336         int nviews = displayed_views();
1337         struct view *base_view = display[0];
1338
1339         if (view == prev && nviews == 1 && !reload) {
1340                 report("Already in %s view", view->name);
1341                 return;
1342         }
1343
1344         if ((reload || strcmp(view->vid, view->id)) &&
1345             !begin_update(view)) {
1346                 report("Failed to load %s view", view->name);
1347                 return;
1348         }
1349
1350         if (split) {
1351                 display[1] = view;
1352                 if (!backgrounded)
1353                         current_view = 1;
1354         } else {
1355                 /* Maximize the current view. */
1356                 memset(display, 0, sizeof(display));
1357                 current_view = 0;
1358                 display[current_view] = view;
1359         }
1360
1361         /* Resize the view when switching between split- and full-screen,
1362          * or when switching between two different full-screen views. */
1363         if (nviews != displayed_views() ||
1364             (nviews == 1 && base_view != display[0]))
1365                 resize_display();
1366
1367         if (split && prev->lineno - prev->offset >= prev->height) {
1368                 /* Take the title line into account. */
1369                 int lines = prev->lineno - prev->offset - prev->height + 1;
1370
1371                 /* Scroll the view that was split if the current line is
1372                  * outside the new limited view. */
1373                 do_scroll_view(prev, lines, TRUE);
1374         }
1375
1376         if (prev && view != prev) {
1377                 if (split && !backgrounded) {
1378                         /* "Blur" the previous view. */
1379                         update_view_title(prev);
1380                 }
1381
1382                 view->parent = prev;
1383         }
1384
1385         if (view == VIEW(REQ_VIEW_HELP))
1386                 load_help_page();
1387
1388         if (view->pipe && view->lines == 0) {
1389                 /* Clear the old view and let the incremental updating refill
1390                  * the screen. */
1391                 wclear(view->win);
1392                 report("");
1393         } else {
1394                 redraw_view(view);
1395                 report("");
1396         }
1397
1398         /* If the view is backgrounded the above calls to report()
1399          * won't redraw the view title. */
1400         if (backgrounded)
1401                 update_view_title(view);
1402 }
1403
1404
1405 /*
1406  * User request switch noodle
1407  */
1408
1409 static int
1410 view_driver(struct view *view, enum request request)
1411 {
1412         int i;
1413
1414         switch (request) {
1415         case REQ_MOVE_UP:
1416         case REQ_MOVE_DOWN:
1417         case REQ_MOVE_PAGE_UP:
1418         case REQ_MOVE_PAGE_DOWN:
1419         case REQ_MOVE_FIRST_LINE:
1420         case REQ_MOVE_LAST_LINE:
1421                 move_view(view, request, TRUE);
1422                 break;
1423
1424         case REQ_SCROLL_LINE_DOWN:
1425         case REQ_SCROLL_LINE_UP:
1426         case REQ_SCROLL_PAGE_DOWN:
1427         case REQ_SCROLL_PAGE_UP:
1428                 scroll_view(view, request);
1429                 break;
1430
1431         case REQ_VIEW_MAIN:
1432         case REQ_VIEW_DIFF:
1433         case REQ_VIEW_LOG:
1434         case REQ_VIEW_HELP:
1435         case REQ_VIEW_PAGER:
1436                 open_view(view, request, OPEN_DEFAULT);
1437                 break;
1438
1439         case REQ_NEXT:
1440         case REQ_PREVIOUS:
1441                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
1442
1443                 if (view == VIEW(REQ_VIEW_DIFF) &&
1444                     view->parent == VIEW(REQ_VIEW_MAIN)) {
1445                         bool redraw = display[1] == view;
1446
1447                         view = view->parent;
1448                         move_view(view, request, redraw);
1449                         if (redraw)
1450                                 update_view_title(view);
1451                 } else {
1452                         move_view(view, request, TRUE);
1453                         break;
1454                 }
1455                 /* Fall-through */
1456
1457         case REQ_ENTER:
1458                 if (!view->lines) {
1459                         report("Nothing to enter");
1460                         break;
1461                 }
1462                 return view->ops->enter(view, &view->line[view->lineno]);
1463
1464         case REQ_VIEW_NEXT:
1465         {
1466                 int nviews = displayed_views();
1467                 int next_view = (current_view + 1) % nviews;
1468
1469                 if (next_view == current_view) {
1470                         report("Only one view is displayed");
1471                         break;
1472                 }
1473
1474                 current_view = next_view;
1475                 /* Blur out the title of the previous view. */
1476                 update_view_title(view);
1477                 report("");
1478                 break;
1479         }
1480         case REQ_TOGGLE_LINENO:
1481                 opt_line_number = !opt_line_number;
1482                 redraw_display();
1483                 break;
1484
1485         case REQ_PROMPT:
1486                 /* Always reload^Wrerun commands from the prompt. */
1487                 open_view(view, opt_request, OPEN_RELOAD);
1488                 break;
1489
1490         case REQ_STOP_LOADING:
1491                 for (i = 0; i < ARRAY_SIZE(views); i++) {
1492                         view = &views[i];
1493                         if (view->pipe)
1494                                 report("Stopped loading the %s view", view->name),
1495                         end_update(view);
1496                 }
1497                 break;
1498
1499         case REQ_SHOW_VERSION:
1500                 report("%s (built %s)", VERSION, __DATE__);
1501                 return TRUE;
1502
1503         case REQ_SCREEN_RESIZE:
1504                 resize_display();
1505                 /* Fall-through */
1506         case REQ_SCREEN_REDRAW:
1507                 redraw_display();
1508                 break;
1509
1510         case REQ_SCREEN_UPDATE:
1511                 doupdate();
1512                 return TRUE;
1513
1514         case REQ_VIEW_CLOSE:
1515                 /* XXX: Mark closed views by letting view->parent point to the
1516                  * view itself. Parents to closed view should never be
1517                  * followed. */
1518                 if (view->parent &&
1519                     view->parent->parent != view->parent) {
1520                         memset(display, 0, sizeof(display));
1521                         current_view = 0;
1522                         display[current_view] = view->parent;
1523                         view->parent = view;
1524                         resize_display();
1525                         redraw_display();
1526                         break;
1527                 }
1528                 /* Fall-through */
1529         case REQ_QUIT:
1530                 return FALSE;
1531
1532         default:
1533                 /* An unknown key will show most commonly used commands. */
1534                 report("Unknown key, press 'h' for help");
1535                 return TRUE;
1536         }
1537
1538         return TRUE;
1539 }
1540
1541
1542 /*
1543  * Pager backend
1544  */
1545
1546 static bool
1547 pager_draw(struct view *view, struct line *line, unsigned int lineno)
1548 {
1549         char *text = line->data;
1550         enum line_type type = line->type;
1551         int textlen = strlen(text);
1552         int attr;
1553
1554         wmove(view->win, lineno, 0);
1555
1556         if (view->offset + lineno == view->lineno) {
1557                 if (type == LINE_COMMIT) {
1558                         string_copy(view->ref, text + 7);
1559                         string_copy(ref_commit, view->ref);
1560                 }
1561
1562                 type = LINE_CURSOR;
1563                 wchgat(view->win, -1, 0, type, NULL);
1564         }
1565
1566         attr = get_line_attr(type);
1567         wattrset(view->win, attr);
1568
1569         if (opt_line_number || opt_tab_size < TABSIZE) {
1570                 static char spaces[] = "                    ";
1571                 int col_offset = 0, col = 0;
1572
1573                 if (opt_line_number) {
1574                         unsigned long real_lineno = view->offset + lineno + 1;
1575
1576                         if (real_lineno == 1 ||
1577                             (real_lineno % opt_num_interval) == 0) {
1578                                 wprintw(view->win, "%.*d", view->digits, real_lineno);
1579
1580                         } else {
1581                                 waddnstr(view->win, spaces,
1582                                          MIN(view->digits, STRING_SIZE(spaces)));
1583                         }
1584                         waddstr(view->win, ": ");
1585                         col_offset = view->digits + 2;
1586                 }
1587
1588                 while (text && col_offset + col < view->width) {
1589                         int cols_max = view->width - col_offset - col;
1590                         char *pos = text;
1591                         int cols;
1592
1593                         if (*text == '\t') {
1594                                 text++;
1595                                 assert(sizeof(spaces) > TABSIZE);
1596                                 pos = spaces;
1597                                 cols = opt_tab_size - (col % opt_tab_size);
1598
1599                         } else {
1600                                 text = strchr(text, '\t');
1601                                 cols = line ? text - pos : strlen(pos);
1602                         }
1603
1604                         waddnstr(view->win, pos, MIN(cols, cols_max));
1605                         col += cols;
1606                 }
1607
1608         } else {
1609                 int col = 0, pos = 0;
1610
1611                 for (; pos < textlen && col < view->width; pos++, col++)
1612                         if (text[pos] == '\t')
1613                                 col += TABSIZE - (col % TABSIZE) - 1;
1614
1615                 waddnstr(view->win, text, pos);
1616         }
1617
1618         return TRUE;
1619 }
1620
1621 static void
1622 add_pager_refs(struct view *view, struct line *line)
1623 {
1624         char buf[1024];
1625         char *data = line->data;
1626         struct ref **refs;
1627         int bufpos = 0, refpos = 0;
1628         const char *sep = "Refs: ";
1629
1630         assert(line->type == LINE_COMMIT);
1631
1632         refs = get_refs(data + STRING_SIZE("commit "));
1633         if (!refs)
1634                 return;
1635
1636         do {
1637                 struct ref *ref = refs[refpos];
1638                 char *fmt = ref->tag ? "%s[%s]" : "%s%s";
1639
1640                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
1641                         return;
1642                 sep = ", ";
1643         } while (refs[refpos++]->next);
1644
1645         if (!realloc_lines(view, view->line_size + 1))
1646                 return;
1647
1648         line = &view->line[view->lines];
1649         line->data = strdup(buf);
1650         if (!line->data)
1651                 return;
1652
1653         line->type = LINE_PP_REFS;
1654         view->lines++;
1655 }
1656
1657 static bool
1658 pager_read(struct view *view, char *data)
1659 {
1660         struct line *line = &view->line[view->lines];
1661
1662         line->data = strdup(data);
1663         if (!line->data)
1664                 return FALSE;
1665
1666         line->type = get_line_type(line->data);
1667         view->lines++;
1668
1669         if (line->type == LINE_COMMIT &&
1670             (view == VIEW(REQ_VIEW_DIFF) ||
1671              view == VIEW(REQ_VIEW_LOG)))
1672                 add_pager_refs(view, line);
1673
1674         return TRUE;
1675 }
1676
1677 static bool
1678 pager_enter(struct view *view, struct line *line)
1679 {
1680         int split = 0;
1681
1682         if (line->type == LINE_COMMIT &&
1683            (view == VIEW(REQ_VIEW_LOG) ||
1684             view == VIEW(REQ_VIEW_PAGER))) {
1685                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
1686                 split = 1;
1687         }
1688
1689         /* Always scroll the view even if it was split. That way
1690          * you can use Enter to scroll through the log view and
1691          * split open each commit diff. */
1692         scroll_view(view, REQ_SCROLL_LINE_DOWN);
1693
1694         /* FIXME: A minor workaround. Scrolling the view will call report("")
1695          * but if we are scrolling a non-current view this won't properly
1696          * update the view title. */
1697         if (split)
1698                 update_view_title(view);
1699
1700         return TRUE;
1701 }
1702
1703 static struct view_ops pager_ops = {
1704         "line",
1705         pager_draw,
1706         pager_read,
1707         pager_enter,
1708 };
1709
1710
1711 /*
1712  * Main view backend
1713  */
1714
1715 struct commit {
1716         char id[41];            /* SHA1 ID. */
1717         char title[75];         /* The first line of the commit message. */
1718         char author[75];        /* The author of the commit. */
1719         struct tm time;         /* Date from the author ident. */
1720         struct ref **refs;      /* Repository references; tags & branch heads. */
1721 };
1722
1723 static bool
1724 main_draw(struct view *view, struct line *line, unsigned int lineno)
1725 {
1726         char buf[DATE_COLS + 1];
1727         struct commit *commit = line->data;
1728         enum line_type type;
1729         int col = 0;
1730         size_t timelen;
1731         size_t authorlen;
1732         int trimmed = 1;
1733
1734         if (!*commit->author)
1735                 return FALSE;
1736
1737         wmove(view->win, lineno, col);
1738
1739         if (view->offset + lineno == view->lineno) {
1740                 string_copy(view->ref, commit->id);
1741                 string_copy(ref_commit, view->ref);
1742                 type = LINE_CURSOR;
1743                 wattrset(view->win, get_line_attr(type));
1744                 wchgat(view->win, -1, 0, type, NULL);
1745
1746         } else {
1747                 type = LINE_MAIN_COMMIT;
1748                 wattrset(view->win, get_line_attr(LINE_MAIN_DATE));
1749         }
1750
1751         timelen = strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time);
1752         waddnstr(view->win, buf, timelen);
1753         waddstr(view->win, " ");
1754
1755         col += DATE_COLS;
1756         wmove(view->win, lineno, col);
1757         if (type != LINE_CURSOR)
1758                 wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR));
1759
1760         if (opt_utf8) {
1761                 authorlen = utf8_length(commit->author, AUTHOR_COLS - 2, &col, &trimmed);
1762         } else {
1763                 authorlen = strlen(commit->author);
1764                 if (authorlen > AUTHOR_COLS - 2) {
1765                         authorlen = AUTHOR_COLS - 2;
1766                         trimmed = 1;
1767                 }
1768         }
1769
1770         if (trimmed) {
1771                 waddnstr(view->win, commit->author, authorlen);
1772                 if (type != LINE_CURSOR)
1773                         wattrset(view->win, get_line_attr(LINE_MAIN_DELIM));
1774                 waddch(view->win, '~');
1775         } else {
1776                 waddstr(view->win, commit->author);
1777         }
1778
1779         col += AUTHOR_COLS;
1780         if (type != LINE_CURSOR)
1781                 wattrset(view->win, A_NORMAL);
1782
1783         mvwaddch(view->win, lineno, col, ACS_LTEE);
1784         wmove(view->win, lineno, col + 2);
1785         col += 2;
1786
1787         if (commit->refs) {
1788                 size_t i = 0;
1789
1790                 do {
1791                         if (type == LINE_CURSOR)
1792                                 ;
1793                         else if (commit->refs[i]->tag)
1794                                 wattrset(view->win, get_line_attr(LINE_MAIN_TAG));
1795                         else
1796                                 wattrset(view->win, get_line_attr(LINE_MAIN_REF));
1797                         waddstr(view->win, "[");
1798                         waddstr(view->win, commit->refs[i]->name);
1799                         waddstr(view->win, "]");
1800                         if (type != LINE_CURSOR)
1801                                 wattrset(view->win, A_NORMAL);
1802                         waddstr(view->win, " ");
1803                         col += strlen(commit->refs[i]->name) + STRING_SIZE("[] ");
1804                 } while (commit->refs[i++]->next);
1805         }
1806
1807         if (type != LINE_CURSOR)
1808                 wattrset(view->win, get_line_attr(type));
1809
1810         {
1811                 int titlelen = strlen(commit->title);
1812
1813                 if (col + titlelen > view->width)
1814                         titlelen = view->width - col;
1815
1816                 waddnstr(view->win, commit->title, titlelen);
1817         }
1818
1819         return TRUE;
1820 }
1821
1822 /* Reads git log --pretty=raw output and parses it into the commit struct. */
1823 static bool
1824 main_read(struct view *view, char *line)
1825 {
1826         enum line_type type = get_line_type(line);
1827         struct commit *commit = view->lines
1828                               ? view->line[view->lines - 1].data : NULL;
1829
1830         switch (type) {
1831         case LINE_COMMIT:
1832                 commit = calloc(1, sizeof(struct commit));
1833                 if (!commit)
1834                         return FALSE;
1835
1836                 line += STRING_SIZE("commit ");
1837
1838                 view->line[view->lines++].data = commit;
1839                 string_copy(commit->id, line);
1840                 commit->refs = get_refs(commit->id);
1841                 break;
1842
1843         case LINE_AUTHOR:
1844         {
1845                 char *ident = line + STRING_SIZE("author ");
1846                 char *end = strchr(ident, '<');
1847
1848                 if (!commit)
1849                         break;
1850
1851                 if (end) {
1852                         for (; end > ident && isspace(end[-1]); end--) ;
1853                         *end = 0;
1854                 }
1855
1856                 string_copy(commit->author, ident);
1857
1858                 /* Parse epoch and timezone */
1859                 if (end) {
1860                         char *secs = strchr(end + 1, '>');
1861                         char *zone;
1862                         time_t time;
1863
1864                         if (!secs || secs[1] != ' ')
1865                                 break;
1866
1867                         secs += 2;
1868                         time = (time_t) atol(secs);
1869                         zone = strchr(secs, ' ');
1870                         if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
1871                                 long tz;
1872
1873                                 zone++;
1874                                 tz  = ('0' - zone[1]) * 60 * 60 * 10;
1875                                 tz += ('0' - zone[2]) * 60 * 60;
1876                                 tz += ('0' - zone[3]) * 60;
1877                                 tz += ('0' - zone[4]) * 60;
1878
1879                                 if (zone[0] == '-')
1880                                         tz = -tz;
1881
1882                                 time -= tz;
1883                         }
1884                         gmtime_r(&time, &commit->time);
1885                 }
1886                 break;
1887         }
1888         default:
1889                 if (!commit)
1890                         break;
1891
1892                 /* Fill in the commit title if it has not already been set. */
1893                 if (commit->title[0])
1894                         break;
1895
1896                 /* Require titles to start with a non-space character at the
1897                  * offset used by git log. */
1898                 /* FIXME: More gracefull handling of titles; append "..." to
1899                  * shortened titles, etc. */
1900                 if (strncmp(line, "    ", 4) ||
1901                     isspace(line[4]))
1902                         break;
1903
1904                 string_copy(commit->title, line + 4);
1905         }
1906
1907         return TRUE;
1908 }
1909
1910 static bool
1911 main_enter(struct view *view, struct line *line)
1912 {
1913         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
1914
1915         open_view(view, REQ_VIEW_DIFF, flags);
1916         return TRUE;
1917 }
1918
1919 static struct view_ops main_ops = {
1920         "commit",
1921         main_draw,
1922         main_read,
1923         main_enter,
1924 };
1925
1926
1927 /*
1928  * Keys
1929  */
1930
1931 struct keymap {
1932         int alias;
1933         int request;
1934 };
1935
1936 static struct keymap keymap[] = {
1937         /* View switching */
1938         { 'm',          REQ_VIEW_MAIN },
1939         { 'd',          REQ_VIEW_DIFF },
1940         { 'l',          REQ_VIEW_LOG },
1941         { 'p',          REQ_VIEW_PAGER },
1942         { 'h',          REQ_VIEW_HELP },
1943         { '?',          REQ_VIEW_HELP },
1944
1945         /* View manipulation */
1946         { 'q',          REQ_VIEW_CLOSE },
1947         { KEY_TAB,      REQ_VIEW_NEXT },
1948         { KEY_RETURN,   REQ_ENTER },
1949         { KEY_UP,       REQ_PREVIOUS },
1950         { KEY_DOWN,     REQ_NEXT },
1951
1952         /* Cursor navigation */
1953         { 'k',          REQ_MOVE_UP },
1954         { 'j',          REQ_MOVE_DOWN },
1955         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
1956         { KEY_END,      REQ_MOVE_LAST_LINE },
1957         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
1958         { ' ',          REQ_MOVE_PAGE_DOWN },
1959         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
1960         { 'b',          REQ_MOVE_PAGE_UP },
1961         { '-',          REQ_MOVE_PAGE_UP },
1962
1963         /* Scrolling */
1964         { KEY_IC,       REQ_SCROLL_LINE_UP },
1965         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
1966         { 'w',          REQ_SCROLL_PAGE_UP },
1967         { 's',          REQ_SCROLL_PAGE_DOWN },
1968
1969         /* Misc */
1970         { 'Q',          REQ_QUIT },
1971         { 'z',          REQ_STOP_LOADING },
1972         { 'v',          REQ_SHOW_VERSION },
1973         { 'r',          REQ_SCREEN_REDRAW },
1974         { 'n',          REQ_TOGGLE_LINENO },
1975         { ':',          REQ_PROMPT },
1976
1977         /* wgetch() with nodelay() enabled returns ERR when there's no input. */
1978         { ERR,          REQ_SCREEN_UPDATE },
1979
1980         /* Use the ncurses SIGWINCH handler. */
1981         { KEY_RESIZE,   REQ_SCREEN_RESIZE },
1982 };
1983
1984 static enum request
1985 get_request(int key)
1986 {
1987         int i;
1988
1989         for (i = 0; i < ARRAY_SIZE(keymap); i++)
1990                 if (keymap[i].alias == key)
1991                         return keymap[i].request;
1992
1993         return (enum request) key;
1994 }
1995
1996 struct key {
1997         char *name;
1998         int value;
1999 };
2000
2001 static struct key key_table[] = {
2002         { "Enter",      KEY_RETURN },
2003         { "Space",      ' ' },
2004         { "Backspace",  KEY_BACKSPACE },
2005         { "Tab",        KEY_TAB },
2006         { "Escape",     KEY_ESC },
2007         { "Left",       KEY_LEFT },
2008         { "Right",      KEY_RIGHT },
2009         { "Up",         KEY_UP },
2010         { "Down",       KEY_DOWN },
2011         { "Insert",     KEY_IC },
2012         { "Delete",     KEY_DC },
2013         { "Home",       KEY_HOME },
2014         { "End",        KEY_END },
2015         { "PageUp",     KEY_PPAGE },
2016         { "PageDown",   KEY_NPAGE },
2017         { "F1",         KEY_F(1) },
2018         { "F2",         KEY_F(2) },
2019         { "F3",         KEY_F(3) },
2020         { "F4",         KEY_F(4) },
2021         { "F5",         KEY_F(5) },
2022         { "F6",         KEY_F(6) },
2023         { "F7",         KEY_F(7) },
2024         { "F8",         KEY_F(8) },
2025         { "F9",         KEY_F(9) },
2026         { "F10",        KEY_F(10) },
2027         { "F11",        KEY_F(11) },
2028         { "F12",        KEY_F(12) },
2029 };
2030
2031 static char *
2032 get_key(enum request request)
2033 {
2034         static char buf[BUFSIZ];
2035         static char key_char[] = "'X'";
2036         int pos = 0;
2037         char *sep = "    ";
2038         int i;
2039
2040         buf[pos] = 0;
2041
2042         for (i = 0; i < ARRAY_SIZE(keymap); i++) {
2043                 char *seq = NULL;
2044                 int key;
2045
2046                 if (keymap[i].request != request)
2047                         continue;
2048
2049                 for (key = 0; key < ARRAY_SIZE(key_table); key++)
2050                         if (key_table[key].value == keymap[i].alias)
2051                                 seq = key_table[key].name;
2052
2053                 if (seq == NULL &&
2054                     keymap[i].alias < 127 &&
2055                     isprint(keymap[i].alias)) {
2056                         key_char[1] = (char) keymap[i].alias;
2057                         seq = key_char;
2058                 }
2059
2060                 if (!seq)
2061                         seq = "'?'";
2062
2063                 if (!string_format_from(buf, &pos, "%s%s", sep, seq))
2064                         return "Too many keybindings!";
2065                 sep = ", ";
2066         }
2067
2068         return buf;
2069 }
2070
2071 static void load_help_page(void)
2072 {
2073         char buf[BUFSIZ];
2074         struct view *view = VIEW(REQ_VIEW_HELP);
2075         int lines = ARRAY_SIZE(req_info) + 2;
2076         int i;
2077
2078         if (view->lines > 0)
2079                 return;
2080
2081         for (i = 0; i < ARRAY_SIZE(req_info); i++)
2082                 if (!req_info[i].request)
2083                         lines++;
2084
2085         view->line = calloc(lines, sizeof(*view->line));
2086         if (!view->line) {
2087                 report("Allocation failure");
2088                 return;
2089         }
2090
2091         pager_read(view, "Quick reference for tig keybindings:");
2092
2093         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
2094                 char *key;
2095
2096                 if (!req_info[i].request) {
2097                         pager_read(view, "");
2098                         pager_read(view, req_info[i].help);
2099                         continue;
2100                 }
2101
2102                 key = get_key(req_info[i].request);
2103                 if (!string_format(buf, "%-25s %s", key, req_info[i].help))
2104                         continue;
2105
2106                 pager_read(view, buf);
2107         }
2108 }
2109
2110
2111 /*
2112  * Unicode / UTF-8 handling
2113  *
2114  * NOTE: Much of the following code for dealing with unicode is derived from
2115  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
2116  * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
2117  */
2118
2119 /* I've (over)annotated a lot of code snippets because I am not entirely
2120  * confident that the approach taken by this small UTF-8 interface is correct.
2121  * --jonas */
2122
2123 static inline int
2124 unicode_width(unsigned long c)
2125 {
2126         if (c >= 0x1100 &&
2127            (c <= 0x115f                         /* Hangul Jamo */
2128             || c == 0x2329
2129             || c == 0x232a
2130             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
2131                                                 /* CJK ... Yi */
2132             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
2133             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
2134             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
2135             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
2136             || (c >= 0xffe0  && c <= 0xffe6)
2137             || (c >= 0x20000 && c <= 0x2fffd)
2138             || (c >= 0x30000 && c <= 0x3fffd)))
2139                 return 2;
2140
2141         return 1;
2142 }
2143
2144 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
2145  * Illegal bytes are set one. */
2146 static const unsigned char utf8_bytes[256] = {
2147         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,
2148         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,
2149         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,
2150         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,
2151         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,
2152         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,
2153         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,
2154         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,
2155 };
2156
2157 /* Decode UTF-8 multi-byte representation into a unicode character. */
2158 static inline unsigned long
2159 utf8_to_unicode(const char *string, size_t length)
2160 {
2161         unsigned long unicode;
2162
2163         switch (length) {
2164         case 1:
2165                 unicode  =   string[0];
2166                 break;
2167         case 2:
2168                 unicode  =  (string[0] & 0x1f) << 6;
2169                 unicode +=  (string[1] & 0x3f);
2170                 break;
2171         case 3:
2172                 unicode  =  (string[0] & 0x0f) << 12;
2173                 unicode += ((string[1] & 0x3f) << 6);
2174                 unicode +=  (string[2] & 0x3f);
2175                 break;
2176         case 4:
2177                 unicode  =  (string[0] & 0x0f) << 18;
2178                 unicode += ((string[1] & 0x3f) << 12);
2179                 unicode += ((string[2] & 0x3f) << 6);
2180                 unicode +=  (string[3] & 0x3f);
2181                 break;
2182         case 5:
2183                 unicode  =  (string[0] & 0x0f) << 24;
2184                 unicode += ((string[1] & 0x3f) << 18);
2185                 unicode += ((string[2] & 0x3f) << 12);
2186                 unicode += ((string[3] & 0x3f) << 6);
2187                 unicode +=  (string[4] & 0x3f);
2188                 break;
2189         case 6:
2190                 unicode  =  (string[0] & 0x01) << 30;
2191                 unicode += ((string[1] & 0x3f) << 24);
2192                 unicode += ((string[2] & 0x3f) << 18);
2193                 unicode += ((string[3] & 0x3f) << 12);
2194                 unicode += ((string[4] & 0x3f) << 6);
2195                 unicode +=  (string[5] & 0x3f);
2196                 break;
2197         default:
2198                 die("Invalid unicode length");
2199         }
2200
2201         /* Invalid characters could return the special 0xfffd value but NUL
2202          * should be just as good. */
2203         return unicode > 0xffff ? 0 : unicode;
2204 }
2205
2206 /* Calculates how much of string can be shown within the given maximum width
2207  * and sets trimmed parameter to non-zero value if all of string could not be
2208  * shown.
2209  *
2210  * Additionally, adds to coloffset how many many columns to move to align with
2211  * the expected position. Takes into account how multi-byte and double-width
2212  * characters will effect the cursor position.
2213  *
2214  * Returns the number of bytes to output from string to satisfy max_width. */
2215 static size_t
2216 utf8_length(const char *string, size_t max_width, int *coloffset, int *trimmed)
2217 {
2218         const char *start = string;
2219         const char *end = strchr(string, '\0');
2220         size_t mbwidth = 0;
2221         size_t width = 0;
2222
2223         *trimmed = 0;
2224
2225         while (string < end) {
2226                 int c = *(unsigned char *) string;
2227                 unsigned char bytes = utf8_bytes[c];
2228                 size_t ucwidth;
2229                 unsigned long unicode;
2230
2231                 if (string + bytes > end)
2232                         break;
2233
2234                 /* Change representation to figure out whether
2235                  * it is a single- or double-width character. */
2236
2237                 unicode = utf8_to_unicode(string, bytes);
2238                 /* FIXME: Graceful handling of invalid unicode character. */
2239                 if (!unicode)
2240                         break;
2241
2242                 ucwidth = unicode_width(unicode);
2243                 width  += ucwidth;
2244                 if (width > max_width) {
2245                         *trimmed = 1;
2246                         break;
2247                 }
2248
2249                 /* The column offset collects the differences between the
2250                  * number of bytes encoding a character and the number of
2251                  * columns will be used for rendering said character.
2252                  *
2253                  * So if some character A is encoded in 2 bytes, but will be
2254                  * represented on the screen using only 1 byte this will and up
2255                  * adding 1 to the multi-byte column offset.
2256                  *
2257                  * Assumes that no double-width character can be encoding in
2258                  * less than two bytes. */
2259                 if (bytes > ucwidth)
2260                         mbwidth += bytes - ucwidth;
2261
2262                 string  += bytes;
2263         }
2264
2265         *coloffset += mbwidth;
2266
2267         return string - start;
2268 }
2269
2270
2271 /*
2272  * Status management
2273  */
2274
2275 /* Whether or not the curses interface has been initialized. */
2276 static bool cursed = FALSE;
2277
2278 /* The status window is used for polling keystrokes. */
2279 static WINDOW *status_win;
2280
2281 /* Update status and title window. */
2282 static void
2283 report(const char *msg, ...)
2284 {
2285         static bool empty = TRUE;
2286         struct view *view = display[current_view];
2287
2288         if (!empty || *msg) {
2289                 va_list args;
2290
2291                 va_start(args, msg);
2292
2293                 werase(status_win);
2294                 wmove(status_win, 0, 0);
2295                 if (*msg) {
2296                         vwprintw(status_win, msg, args);
2297                         empty = FALSE;
2298                 } else {
2299                         empty = TRUE;
2300                 }
2301                 wrefresh(status_win);
2302
2303                 va_end(args);
2304         }
2305
2306         update_view_title(view);
2307         update_display_cursor();
2308 }
2309
2310 /* Controls when nodelay should be in effect when polling user input. */
2311 static void
2312 set_nonblocking_input(bool loading)
2313 {
2314         static unsigned int loading_views;
2315
2316         if ((loading == FALSE && loading_views-- == 1) ||
2317             (loading == TRUE  && loading_views++ == 0))
2318                 nodelay(status_win, loading);
2319 }
2320
2321 static void
2322 init_display(void)
2323 {
2324         int x, y;
2325
2326         /* Initialize the curses library */
2327         if (isatty(STDIN_FILENO)) {
2328                 cursed = !!initscr();
2329         } else {
2330                 /* Leave stdin and stdout alone when acting as a pager. */
2331                 FILE *io = fopen("/dev/tty", "r+");
2332
2333                 cursed = !!newterm(NULL, io, io);
2334         }
2335
2336         if (!cursed)
2337                 die("Failed to initialize curses");
2338
2339         nonl();         /* Tell curses not to do NL->CR/NL on output */
2340         cbreak();       /* Take input chars one at a time, no wait for \n */
2341         noecho();       /* Don't echo input */
2342         leaveok(stdscr, TRUE);
2343
2344         if (has_colors())
2345                 init_colors();
2346
2347         getmaxyx(stdscr, y, x);
2348         status_win = newwin(1, 0, y - 1, 0);
2349         if (!status_win)
2350                 die("Failed to create status window");
2351
2352         /* Enable keyboard mapping */
2353         keypad(status_win, TRUE);
2354         wbkgdset(status_win, get_line_attr(LINE_STATUS));
2355 }
2356
2357
2358 /*
2359  * Repository references
2360  */
2361
2362 static struct ref *refs;
2363 static size_t refs_size;
2364
2365 /* Id <-> ref store */
2366 static struct ref ***id_refs;
2367 static size_t id_refs_size;
2368
2369 static struct ref **
2370 get_refs(char *id)
2371 {
2372         struct ref ***tmp_id_refs;
2373         struct ref **ref_list = NULL;
2374         size_t ref_list_size = 0;
2375         size_t i;
2376
2377         for (i = 0; i < id_refs_size; i++)
2378                 if (!strcmp(id, id_refs[i][0]->id))
2379                         return id_refs[i];
2380
2381         tmp_id_refs = realloc(id_refs, (id_refs_size + 1) * sizeof(*id_refs));
2382         if (!tmp_id_refs)
2383                 return NULL;
2384
2385         id_refs = tmp_id_refs;
2386
2387         for (i = 0; i < refs_size; i++) {
2388                 struct ref **tmp;
2389
2390                 if (strcmp(id, refs[i].id))
2391                         continue;
2392
2393                 tmp = realloc(ref_list, (ref_list_size + 1) * sizeof(*ref_list));
2394                 if (!tmp) {
2395                         if (ref_list)
2396                                 free(ref_list);
2397                         return NULL;
2398                 }
2399
2400                 ref_list = tmp;
2401                 if (ref_list_size > 0)
2402                         ref_list[ref_list_size - 1]->next = 1;
2403                 ref_list[ref_list_size] = &refs[i];
2404
2405                 /* XXX: The properties of the commit chains ensures that we can
2406                  * safely modify the shared ref. The repo references will
2407                  * always be similar for the same id. */
2408                 ref_list[ref_list_size]->next = 0;
2409                 ref_list_size++;
2410         }
2411
2412         if (ref_list)
2413                 id_refs[id_refs_size++] = ref_list;
2414
2415         return ref_list;
2416 }
2417
2418 static int
2419 read_ref(char *id, int idlen, char *name, int namelen)
2420 {
2421         struct ref *ref;
2422         bool tag = FALSE;
2423         bool tag_commit = FALSE;
2424
2425         /* Commits referenced by tags has "^{}" appended. */
2426         if (name[namelen - 1] == '}') {
2427                 while (namelen > 0 && name[namelen] != '^')
2428                         namelen--;
2429                 if (namelen > 0)
2430                         tag_commit = TRUE;
2431                 name[namelen] = 0;
2432         }
2433
2434         if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
2435                 if (!tag_commit)
2436                         return OK;
2437                 name += STRING_SIZE("refs/tags/");
2438                 tag = TRUE;
2439
2440         } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
2441                 name += STRING_SIZE("refs/heads/");
2442
2443         } else if (!strcmp(name, "HEAD")) {
2444                 return OK;
2445         }
2446
2447         refs = realloc(refs, sizeof(*refs) * (refs_size + 1));
2448         if (!refs)
2449                 return ERR;
2450
2451         ref = &refs[refs_size++];
2452         ref->name = strdup(name);
2453         if (!ref->name)
2454                 return ERR;
2455
2456         ref->tag = tag;
2457         string_copy(ref->id, id);
2458
2459         return OK;
2460 }
2461
2462 static int
2463 load_refs(void)
2464 {
2465         const char *cmd_env = getenv("TIG_LS_REMOTE");
2466         const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
2467
2468         return read_properties(popen(cmd, "r"), "\t", read_ref);
2469 }
2470
2471 static int
2472 read_repo_config_option(char *name, int namelen, char *value, int valuelen)
2473 {
2474         if (!strcmp(name, "i18n.commitencoding"))
2475                 string_copy(opt_encoding, value);
2476
2477         return OK;
2478 }
2479
2480 static int
2481 load_repo_config(void)
2482 {
2483         return read_properties(popen("git repo-config --list", "r"),
2484                                "=", read_repo_config_option);
2485 }
2486
2487 static int
2488 read_properties(FILE *pipe, const char *separators,
2489                 int (*read_property)(char *, int, char *, int))
2490 {
2491         char buffer[BUFSIZ];
2492         char *name;
2493         int state = OK;
2494
2495         if (!pipe)
2496                 return ERR;
2497
2498         while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
2499                 char *value;
2500                 size_t namelen;
2501                 size_t valuelen;
2502
2503                 name = chomp_string(name);
2504                 namelen = strcspn(name, separators);
2505
2506                 if (name[namelen]) {
2507                         name[namelen] = 0;
2508                         value = chomp_string(name + namelen + 1);
2509                         valuelen = strlen(value);
2510
2511                 } else {
2512                         value = "";
2513                         valuelen = 0;
2514                 }
2515
2516                 state = read_property(name, namelen, value, valuelen);
2517         }
2518
2519         if (state != ERR && ferror(pipe))
2520                 state = ERR;
2521
2522         pclose(pipe);
2523
2524         return state;
2525 }
2526
2527
2528 /*
2529  * Main
2530  */
2531
2532 #if __GNUC__ >= 3
2533 #define __NORETURN __attribute__((__noreturn__))
2534 #else
2535 #define __NORETURN
2536 #endif
2537
2538 static void __NORETURN
2539 quit(int sig)
2540 {
2541         /* XXX: Restore tty modes and let the OS cleanup the rest! */
2542         if (cursed)
2543                 endwin();
2544         exit(0);
2545 }
2546
2547 static void __NORETURN
2548 die(const char *err, ...)
2549 {
2550         va_list args;
2551
2552         endwin();
2553
2554         va_start(args, err);
2555         fputs("tig: ", stderr);
2556         vfprintf(stderr, err, args);
2557         fputs("\n", stderr);
2558         va_end(args);
2559
2560         exit(1);
2561 }
2562
2563 int
2564 main(int argc, char *argv[])
2565 {
2566         struct view *view;
2567         enum request request;
2568         size_t i;
2569
2570         signal(SIGINT, quit);
2571
2572         if (load_options() == ERR)
2573                 die("Failed to load user config.");
2574
2575         /* Load the repo config file so options can be overwritten from
2576          * the command line.  */
2577         if (load_repo_config() == ERR)
2578                 die("Failed to load repo config.");
2579
2580         if (!parse_options(argc, argv))
2581                 return 0;
2582
2583         if (load_refs() == ERR)
2584                 die("Failed to load refs.");
2585
2586         /* Require a git repository unless when running in pager mode. */
2587         if (refs_size == 0 && opt_request != REQ_VIEW_PAGER)
2588                 die("Not a git repository");
2589
2590         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2591                 view->cmd_env = getenv(view->cmd_env);
2592
2593         request = opt_request;
2594
2595         init_display();
2596
2597         while (view_driver(display[current_view], request)) {
2598                 int key;
2599                 int i;
2600
2601                 foreach_view (view, i)
2602                         update_view(view);
2603
2604                 /* Refresh, accept single keystroke of input */
2605                 key = wgetch(status_win);
2606                 request = get_request(key);
2607
2608                 /* Some low-level request handling. This keeps access to
2609                  * status_win restricted. */
2610                 switch (request) {
2611                 case REQ_PROMPT:
2612                         report(":");
2613                         /* Temporarily switch to line-oriented and echoed
2614                          * input. */
2615                         nocbreak();
2616                         echo();
2617
2618                         if (wgetnstr(status_win, opt_cmd + 4, sizeof(opt_cmd) - 4) == OK) {
2619                                 memcpy(opt_cmd, "git ", 4);
2620                                 opt_request = REQ_VIEW_PAGER;
2621                         } else {
2622                                 report("Prompt interrupted by loading view, "
2623                                        "press 'z' to stop loading views");
2624                                 request = REQ_SCREEN_UPDATE;
2625                         }
2626
2627                         noecho();
2628                         cbreak();
2629                         break;
2630
2631                 case REQ_SCREEN_RESIZE:
2632                 {
2633                         int height, width;
2634
2635                         getmaxyx(stdscr, height, width);
2636
2637                         /* Resize the status view and let the view driver take
2638                          * care of resizing the displayed views. */
2639                         wresize(status_win, 1, width);
2640                         mvwin(status_win, height - 1, 0);
2641                         wrefresh(status_win);
2642                         break;
2643                 }
2644                 default:
2645                         break;
2646                 }
2647         }
2648
2649         quit(0);
2650
2651         return 0;
2652 }