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