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