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