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