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