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