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