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