Documentation update
[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         { "Hash",       '#' },
774         { "Home",       KEY_HOME },
775         { "End",        KEY_END },
776         { "PageUp",     KEY_PPAGE },
777         { "PageDown",   KEY_NPAGE },
778         { "F1",         KEY_F(1) },
779         { "F2",         KEY_F(2) },
780         { "F3",         KEY_F(3) },
781         { "F4",         KEY_F(4) },
782         { "F5",         KEY_F(5) },
783         { "F6",         KEY_F(6) },
784         { "F7",         KEY_F(7) },
785         { "F8",         KEY_F(8) },
786         { "F9",         KEY_F(9) },
787         { "F10",        KEY_F(10) },
788         { "F11",        KEY_F(11) },
789         { "F12",        KEY_F(12) },
790 };
791
792 static int
793 get_key_value(const char *name)
794 {
795         int i;
796
797         for (i = 0; i < ARRAY_SIZE(key_table); i++)
798                 if (!strcasecmp(key_table[i].name, name))
799                         return key_table[i].value;
800
801         if (strlen(name) == 1 && isprint(*name))
802                 return (int) *name;
803
804         return ERR;
805 }
806
807 static char *
808 get_key(enum request request)
809 {
810         static char buf[BUFSIZ];
811         static char key_char[] = "'X'";
812         int pos = 0;
813         char *sep = "    ";
814         int i;
815
816         buf[pos] = 0;
817
818         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
819                 struct keybinding *keybinding = &default_keybindings[i];
820                 char *seq = NULL;
821                 int key;
822
823                 if (keybinding->request != request)
824                         continue;
825
826                 for (key = 0; key < ARRAY_SIZE(key_table); key++)
827                         if (key_table[key].value == keybinding->alias)
828                                 seq = key_table[key].name;
829
830                 if (seq == NULL &&
831                     keybinding->alias < 127 &&
832                     isprint(keybinding->alias)) {
833                         key_char[1] = (char) keybinding->alias;
834                         seq = key_char;
835                 }
836
837                 if (!seq)
838                         seq = "'?'";
839
840                 if (!string_format_from(buf, &pos, "%s%s", sep, seq))
841                         return "Too many keybindings!";
842                 sep = ", ";
843         }
844
845         return buf;
846 }
847
848
849 /*
850  * User config file handling.
851  */
852
853 static struct int_map color_map[] = {
854 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
855         COLOR_MAP(DEFAULT),
856         COLOR_MAP(BLACK),
857         COLOR_MAP(BLUE),
858         COLOR_MAP(CYAN),
859         COLOR_MAP(GREEN),
860         COLOR_MAP(MAGENTA),
861         COLOR_MAP(RED),
862         COLOR_MAP(WHITE),
863         COLOR_MAP(YELLOW),
864 };
865
866 #define set_color(color, name) \
867         set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
868
869 static struct int_map attr_map[] = {
870 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
871         ATTR_MAP(NORMAL),
872         ATTR_MAP(BLINK),
873         ATTR_MAP(BOLD),
874         ATTR_MAP(DIM),
875         ATTR_MAP(REVERSE),
876         ATTR_MAP(STANDOUT),
877         ATTR_MAP(UNDERLINE),
878 };
879
880 #define set_attribute(attr, name) \
881         set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
882
883 static int   config_lineno;
884 static bool  config_errors;
885 static char *config_msg;
886
887 /* Wants: object fgcolor bgcolor [attr] */
888 static int
889 option_color_command(int argc, char *argv[])
890 {
891         struct line_info *info;
892
893         if (argc != 3 && argc != 4) {
894                 config_msg = "Wrong number of arguments given to color command";
895                 return ERR;
896         }
897
898         info = get_line_info(argv[0], strlen(argv[0]));
899         if (!info) {
900                 config_msg = "Unknown color name";
901                 return ERR;
902         }
903
904         if (set_color(&info->fg, argv[1]) == ERR ||
905             set_color(&info->bg, argv[2]) == ERR) {
906                 config_msg = "Unknown color";
907                 return ERR;
908         }
909
910         if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
911                 config_msg = "Unknown attribute";
912                 return ERR;
913         }
914
915         return OK;
916 }
917
918 /* Wants: name = value */
919 static int
920 option_set_command(int argc, char *argv[])
921 {
922         if (argc != 3) {
923                 config_msg = "Wrong number of arguments given to set command";
924                 return ERR;
925         }
926
927         if (strcmp(argv[1], "=")) {
928                 config_msg = "No value assigned";
929                 return ERR;
930         }
931
932         if (!strcmp(argv[0], "show-rev-graph")) {
933                 opt_rev_graph = (!strcmp(argv[2], "1") ||
934                                  !strcmp(argv[2], "true") ||
935                                  !strcmp(argv[2], "yes"));
936                 return OK;
937         }
938
939         if (!strcmp(argv[0], "line-number-interval")) {
940                 opt_num_interval = atoi(argv[2]);
941                 return OK;
942         }
943
944         if (!strcmp(argv[0], "tab-size")) {
945                 opt_tab_size = atoi(argv[2]);
946                 return OK;
947         }
948
949         if (!strcmp(argv[0], "commit-encoding")) {
950                 char *arg = argv[2];
951                 int delimiter = *arg;
952                 int i;
953
954                 switch (delimiter) {
955                 case '"':
956                 case '\'':
957                         for (arg++, i = 0; arg[i]; i++)
958                                 if (arg[i] == delimiter) {
959                                         arg[i] = 0;
960                                         break;
961                                 }
962                 default:
963                         string_copy(opt_encoding, arg);
964                         return OK;
965                 }
966         }
967
968         config_msg = "Unknown variable name";
969         return ERR;
970 }
971
972 /* Wants: mode request key */
973 static int
974 option_bind_command(int argc, char *argv[])
975 {
976         enum request request;
977         int keymap;
978         int key;
979
980         if (argc != 3) {
981                 config_msg = "Wrong number of arguments given to bind command";
982                 return ERR;
983         }
984
985         if (set_keymap(&keymap, argv[0]) == ERR) {
986                 config_msg = "Unknown key map";
987                 return ERR;
988         }
989
990         key = get_key_value(argv[1]);
991         if (key == ERR) {
992                 config_msg = "Unknown key";
993                 return ERR;
994         }
995
996         request = get_request(argv[2]);
997         if (request == REQ_UNKNOWN) {
998                 config_msg = "Unknown request name";
999                 return ERR;
1000         }
1001
1002         add_keybinding(keymap, request, key);
1003
1004         return OK;
1005 }
1006
1007 static int
1008 set_option(char *opt, char *value)
1009 {
1010         char *argv[16];
1011         int valuelen;
1012         int argc = 0;
1013
1014         /* Tokenize */
1015         while (argc < ARRAY_SIZE(argv) && (valuelen = strcspn(value, " \t"))) {
1016                 argv[argc++] = value;
1017
1018                 value += valuelen;
1019                 if (!*value)
1020                         break;
1021
1022                 *value++ = 0;
1023                 while (isspace(*value))
1024                         value++;
1025         }
1026
1027         if (!strcmp(opt, "color"))
1028                 return option_color_command(argc, argv);
1029
1030         if (!strcmp(opt, "set"))
1031                 return option_set_command(argc, argv);
1032
1033         if (!strcmp(opt, "bind"))
1034                 return option_bind_command(argc, argv);
1035
1036         config_msg = "Unknown option command";
1037         return ERR;
1038 }
1039
1040 static int
1041 read_option(char *opt, int optlen, char *value, int valuelen)
1042 {
1043         int status = OK;
1044
1045         config_lineno++;
1046         config_msg = "Internal error";
1047
1048         /* Check for comment markers, since read_properties() will
1049          * only ensure opt and value are split at first " \t". */
1050         optlen = strcspn(opt, "#");
1051         if (optlen == 0)
1052                 return OK;
1053
1054         if (opt[optlen] != 0) {
1055                 config_msg = "No option value";
1056                 status = ERR;
1057
1058         }  else {
1059                 /* Look for comment endings in the value. */
1060                 int len = strcspn(value, "#");
1061
1062                 if (len < valuelen) {
1063                         valuelen = len;
1064                         value[valuelen] = 0;
1065                 }
1066
1067                 status = set_option(opt, value);
1068         }
1069
1070         if (status == ERR) {
1071                 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1072                         config_lineno, optlen, opt, config_msg);
1073                 config_errors = TRUE;
1074         }
1075
1076         /* Always keep going if errors are encountered. */
1077         return OK;
1078 }
1079
1080 static int
1081 load_options(void)
1082 {
1083         char *home = getenv("HOME");
1084         char buf[1024];
1085         FILE *file;
1086
1087         config_lineno = 0;
1088         config_errors = FALSE;
1089
1090         if (!home || !string_format(buf, "%s/.tigrc", home))
1091                 return ERR;
1092
1093         /* It's ok that the file doesn't exist. */
1094         file = fopen(buf, "r");
1095         if (!file)
1096                 return OK;
1097
1098         if (read_properties(file, " \t", read_option) == ERR ||
1099             config_errors == TRUE)
1100                 fprintf(stderr, "Errors while loading %s.\n", buf);
1101
1102         return OK;
1103 }
1104
1105
1106 /*
1107  * The viewer
1108  */
1109
1110 struct view;
1111 struct view_ops;
1112
1113 /* The display array of active views and the index of the current view. */
1114 static struct view *display[2];
1115 static unsigned int current_view;
1116
1117 #define foreach_view(view, i) \
1118         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1119
1120 #define displayed_views()       (display[1] != NULL ? 2 : 1)
1121
1122 /* Current head and commit ID */
1123 static char ref_commit[SIZEOF_REF]      = "HEAD";
1124 static char ref_head[SIZEOF_REF]        = "HEAD";
1125
1126 struct view {
1127         const char *name;       /* View name */
1128         const char *cmd_fmt;    /* Default command line format */
1129         const char *cmd_env;    /* Command line set via environment */
1130         const char *id;         /* Points to either of ref_{head,commit} */
1131
1132         struct view_ops *ops;   /* View operations */
1133
1134         enum keymap keymap;     /* What keymap does this view have */
1135
1136         char cmd[SIZEOF_CMD];   /* Command buffer */
1137         char ref[SIZEOF_REF];   /* Hovered commit reference */
1138         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
1139
1140         int height, width;      /* The width and height of the main window */
1141         WINDOW *win;            /* The main window */
1142         WINDOW *title;          /* The title window living below the main window */
1143
1144         /* Navigation */
1145         unsigned long offset;   /* Offset of the window top */
1146         unsigned long lineno;   /* Current line number */
1147
1148         /* If non-NULL, points to the view that opened this view. If this view
1149          * is closed tig will switch back to the parent view. */
1150         struct view *parent;
1151
1152         /* Buffering */
1153         unsigned long lines;    /* Total number of lines */
1154         struct line *line;      /* Line index */
1155         unsigned long line_size;/* Total number of allocated lines */
1156         unsigned int digits;    /* Number of digits in the lines member. */
1157
1158         /* Loading */
1159         FILE *pipe;
1160         time_t start_time;
1161 };
1162
1163 struct view_ops {
1164         /* What type of content being displayed. Used in the title bar. */
1165         const char *type;
1166         /* Draw one line; @lineno must be < view->height. */
1167         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1168         /* Read one line; updates view->line. */
1169         bool (*read)(struct view *view, char *data);
1170         /* Depending on view, change display based on current line. */
1171         bool (*enter)(struct view *view, struct line *line);
1172 };
1173
1174 static struct view_ops pager_ops;
1175 static struct view_ops main_ops;
1176
1177 #define VIEW_STR(name, cmd, env, ref, ops, map) \
1178         { name, cmd, #env, ref, ops, map}
1179
1180 #define VIEW_(id, name, ops, ref) \
1181         VIEW_STR(name, TIG_##id##_CMD,  TIG_##id##_CMD, ref, ops, KEYMAP_##id)
1182
1183
1184 static struct view views[] = {
1185         VIEW_(MAIN,  "main",  &main_ops,  ref_head),
1186         VIEW_(DIFF,  "diff",  &pager_ops, ref_commit),
1187         VIEW_(LOG,   "log",   &pager_ops, ref_head),
1188         VIEW_(HELP,  "help",  &pager_ops, "static"),
1189         VIEW_(PAGER, "pager", &pager_ops, "static"),
1190 };
1191
1192 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1193
1194
1195 static bool
1196 draw_view_line(struct view *view, unsigned int lineno)
1197 {
1198         if (view->offset + lineno >= view->lines)
1199                 return FALSE;
1200
1201         return view->ops->draw(view, &view->line[view->offset + lineno], lineno);
1202 }
1203
1204 static void
1205 redraw_view_from(struct view *view, int lineno)
1206 {
1207         assert(0 <= lineno && lineno < view->height);
1208
1209         for (; lineno < view->height; lineno++) {
1210                 if (!draw_view_line(view, lineno))
1211                         break;
1212         }
1213
1214         redrawwin(view->win);
1215         wrefresh(view->win);
1216 }
1217
1218 static void
1219 redraw_view(struct view *view)
1220 {
1221         wclear(view->win);
1222         redraw_view_from(view, 0);
1223 }
1224
1225
1226 static void
1227 update_view_title(struct view *view)
1228 {
1229         if (view == display[current_view])
1230                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
1231         else
1232                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
1233
1234         werase(view->title);
1235         wmove(view->title, 0, 0);
1236
1237         if (*view->ref)
1238                 wprintw(view->title, "[%s] %s", view->name, view->ref);
1239         else
1240                 wprintw(view->title, "[%s]", view->name);
1241
1242         if (view->lines || view->pipe) {
1243                 unsigned int view_lines = view->offset + view->height;
1244                 unsigned int lines = view->lines
1245                                    ? MIN(view_lines, view->lines) * 100 / view->lines
1246                                    : 0;
1247
1248                 wprintw(view->title, " - %s %d of %d (%d%%)",
1249                         view->ops->type,
1250                         view->lineno + 1,
1251                         view->lines,
1252                         lines);
1253         }
1254
1255         if (view->pipe) {
1256                 time_t secs = time(NULL) - view->start_time;
1257
1258                 /* Three git seconds are a long time ... */
1259                 if (secs > 2)
1260                         wprintw(view->title, " %lds", secs);
1261         }
1262
1263         wmove(view->title, 0, view->width - 1);
1264         wrefresh(view->title);
1265 }
1266
1267 static void
1268 resize_display(void)
1269 {
1270         int offset, i;
1271         struct view *base = display[0];
1272         struct view *view = display[1] ? display[1] : display[0];
1273
1274         /* Setup window dimensions */
1275
1276         getmaxyx(stdscr, base->height, base->width);
1277
1278         /* Make room for the status window. */
1279         base->height -= 1;
1280
1281         if (view != base) {
1282                 /* Horizontal split. */
1283                 view->width   = base->width;
1284                 view->height  = SCALE_SPLIT_VIEW(base->height);
1285                 base->height -= view->height;
1286
1287                 /* Make room for the title bar. */
1288                 view->height -= 1;
1289         }
1290
1291         /* Make room for the title bar. */
1292         base->height -= 1;
1293
1294         offset = 0;
1295
1296         foreach_view (view, i) {
1297                 if (!view->win) {
1298                         view->win = newwin(view->height, 0, offset, 0);
1299                         if (!view->win)
1300                                 die("Failed to create %s view", view->name);
1301
1302                         scrollok(view->win, TRUE);
1303
1304                         view->title = newwin(1, 0, offset + view->height, 0);
1305                         if (!view->title)
1306                                 die("Failed to create title window");
1307
1308                 } else {
1309                         wresize(view->win, view->height, view->width);
1310                         mvwin(view->win,   offset, 0);
1311                         mvwin(view->title, offset + view->height, 0);
1312                 }
1313
1314                 offset += view->height + 1;
1315         }
1316 }
1317
1318 static void
1319 redraw_display(void)
1320 {
1321         struct view *view;
1322         int i;
1323
1324         foreach_view (view, i) {
1325                 redraw_view(view);
1326                 update_view_title(view);
1327         }
1328 }
1329
1330 static void
1331 update_display_cursor(void)
1332 {
1333         struct view *view = display[current_view];
1334
1335         /* Move the cursor to the right-most column of the cursor line.
1336          *
1337          * XXX: This could turn out to be a bit expensive, but it ensures that
1338          * the cursor does not jump around. */
1339         if (view->lines) {
1340                 wmove(view->win, view->lineno - view->offset, view->width - 1);
1341                 wrefresh(view->win);
1342         }
1343 }
1344
1345 /*
1346  * Navigation
1347  */
1348
1349 /* Scrolling backend */
1350 static void
1351 do_scroll_view(struct view *view, int lines, bool redraw)
1352 {
1353         /* The rendering expects the new offset. */
1354         view->offset += lines;
1355
1356         assert(0 <= view->offset && view->offset < view->lines);
1357         assert(lines);
1358
1359         /* Redraw the whole screen if scrolling is pointless. */
1360         if (view->height < ABS(lines)) {
1361                 redraw_view(view);
1362
1363         } else {
1364                 int line = lines > 0 ? view->height - lines : 0;
1365                 int end = line + ABS(lines);
1366
1367                 wscrl(view->win, lines);
1368
1369                 for (; line < end; line++) {
1370                         if (!draw_view_line(view, line))
1371                                 break;
1372                 }
1373         }
1374
1375         /* Move current line into the view. */
1376         if (view->lineno < view->offset) {
1377                 view->lineno = view->offset;
1378                 draw_view_line(view, 0);
1379
1380         } else if (view->lineno >= view->offset + view->height) {
1381                 if (view->lineno == view->offset + view->height) {
1382                         /* Clear the hidden line so it doesn't show if the view
1383                          * is scrolled up. */
1384                         wmove(view->win, view->height, 0);
1385                         wclrtoeol(view->win);
1386                 }
1387                 view->lineno = view->offset + view->height - 1;
1388                 draw_view_line(view, view->lineno - view->offset);
1389         }
1390
1391         assert(view->offset <= view->lineno && view->lineno < view->lines);
1392
1393         if (!redraw)
1394                 return;
1395
1396         redrawwin(view->win);
1397         wrefresh(view->win);
1398         report("");
1399 }
1400
1401 /* Scroll frontend */
1402 static void
1403 scroll_view(struct view *view, enum request request)
1404 {
1405         int lines = 1;
1406
1407         switch (request) {
1408         case REQ_SCROLL_PAGE_DOWN:
1409                 lines = view->height;
1410         case REQ_SCROLL_LINE_DOWN:
1411                 if (view->offset + lines > view->lines)
1412                         lines = view->lines - view->offset;
1413
1414                 if (lines == 0 || view->offset + view->height >= view->lines) {
1415                         report("Cannot scroll beyond the last line");
1416                         return;
1417                 }
1418                 break;
1419
1420         case REQ_SCROLL_PAGE_UP:
1421                 lines = view->height;
1422         case REQ_SCROLL_LINE_UP:
1423                 if (lines > view->offset)
1424                         lines = view->offset;
1425
1426                 if (lines == 0) {
1427                         report("Cannot scroll beyond the first line");
1428                         return;
1429                 }
1430
1431                 lines = -lines;
1432                 break;
1433
1434         default:
1435                 die("request %d not handled in switch", request);
1436         }
1437
1438         do_scroll_view(view, lines, TRUE);
1439 }
1440
1441 /* Cursor moving */
1442 static void
1443 move_view(struct view *view, enum request request, bool redraw)
1444 {
1445         int steps;
1446
1447         switch (request) {
1448         case REQ_MOVE_FIRST_LINE:
1449                 steps = -view->lineno;
1450                 break;
1451
1452         case REQ_MOVE_LAST_LINE:
1453                 steps = view->lines - view->lineno - 1;
1454                 break;
1455
1456         case REQ_MOVE_PAGE_UP:
1457                 steps = view->height > view->lineno
1458                       ? -view->lineno : -view->height;
1459                 break;
1460
1461         case REQ_MOVE_PAGE_DOWN:
1462                 steps = view->lineno + view->height >= view->lines
1463                       ? view->lines - view->lineno - 1 : view->height;
1464                 break;
1465
1466         case REQ_MOVE_UP:
1467                 steps = -1;
1468                 break;
1469
1470         case REQ_MOVE_DOWN:
1471                 steps = 1;
1472                 break;
1473
1474         default:
1475                 die("request %d not handled in switch", request);
1476         }
1477
1478         if (steps <= 0 && view->lineno == 0) {
1479                 report("Cannot move beyond the first line");
1480                 return;
1481
1482         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
1483                 report("Cannot move beyond the last line");
1484                 return;
1485         }
1486
1487         /* Move the current line */
1488         view->lineno += steps;
1489         assert(0 <= view->lineno && view->lineno < view->lines);
1490
1491         /* Repaint the old "current" line if we be scrolling */
1492         if (ABS(steps) < view->height) {
1493                 int prev_lineno = view->lineno - steps - view->offset;
1494
1495                 wmove(view->win, prev_lineno, 0);
1496                 wclrtoeol(view->win);
1497                 draw_view_line(view,  prev_lineno);
1498         }
1499
1500         /* Check whether the view needs to be scrolled */
1501         if (view->lineno < view->offset ||
1502             view->lineno >= view->offset + view->height) {
1503                 if (steps < 0 && -steps > view->offset) {
1504                         steps = -view->offset;
1505
1506                 } else if (steps > 0) {
1507                         if (view->lineno == view->lines - 1 &&
1508                             view->lines > view->height) {
1509                                 steps = view->lines - view->offset - 1;
1510                                 if (steps >= view->height)
1511                                         steps -= view->height - 1;
1512                         }
1513                 }
1514
1515                 do_scroll_view(view, steps, redraw);
1516                 return;
1517         }
1518
1519         /* Draw the current line */
1520         draw_view_line(view, view->lineno - view->offset);
1521
1522         if (!redraw)
1523                 return;
1524
1525         redrawwin(view->win);
1526         wrefresh(view->win);
1527         report("");
1528 }
1529
1530
1531 /*
1532  * Incremental updating
1533  */
1534
1535 static void
1536 end_update(struct view *view)
1537 {
1538         if (!view->pipe)
1539                 return;
1540         set_nonblocking_input(FALSE);
1541         if (view->pipe == stdin)
1542                 fclose(view->pipe);
1543         else
1544                 pclose(view->pipe);
1545         view->pipe = NULL;
1546 }
1547
1548 static bool
1549 begin_update(struct view *view)
1550 {
1551         const char *id = view->id;
1552
1553         if (view->pipe)
1554                 end_update(view);
1555
1556         if (opt_cmd[0]) {
1557                 string_copy(view->cmd, opt_cmd);
1558                 opt_cmd[0] = 0;
1559                 /* When running random commands, the view ref could have become
1560                  * invalid so clear it. */
1561                 view->ref[0] = 0;
1562         } else {
1563                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
1564
1565                 if (!string_format(view->cmd, format, id, id, id, id, id))
1566                         return FALSE;
1567         }
1568
1569         /* Special case for the pager view. */
1570         if (opt_pipe) {
1571                 view->pipe = opt_pipe;
1572                 opt_pipe = NULL;
1573         } else {
1574                 view->pipe = popen(view->cmd, "r");
1575         }
1576
1577         if (!view->pipe)
1578                 return FALSE;
1579
1580         set_nonblocking_input(TRUE);
1581
1582         view->offset = 0;
1583         view->lines  = 0;
1584         view->lineno = 0;
1585         string_copy(view->vid, id);
1586
1587         if (view->line) {
1588                 int i;
1589
1590                 for (i = 0; i < view->lines; i++)
1591                         if (view->line[i].data)
1592                                 free(view->line[i].data);
1593
1594                 free(view->line);
1595                 view->line = NULL;
1596         }
1597
1598         view->start_time = time(NULL);
1599
1600         return TRUE;
1601 }
1602
1603 static struct line *
1604 realloc_lines(struct view *view, size_t line_size)
1605 {
1606         struct line *tmp = realloc(view->line, sizeof(*view->line) * line_size);
1607
1608         if (!tmp)
1609                 return NULL;
1610
1611         view->line = tmp;
1612         view->line_size = line_size;
1613         return view->line;
1614 }
1615
1616 static bool
1617 update_view(struct view *view)
1618 {
1619         char buffer[BUFSIZ];
1620         char *line;
1621         /* The number of lines to read. If too low it will cause too much
1622          * redrawing (and possible flickering), if too high responsiveness
1623          * will suffer. */
1624         unsigned long lines = view->height;
1625         int redraw_from = -1;
1626
1627         if (!view->pipe)
1628                 return TRUE;
1629
1630         /* Only redraw if lines are visible. */
1631         if (view->offset + view->height >= view->lines)
1632                 redraw_from = view->lines - view->offset;
1633
1634         if (!realloc_lines(view, view->lines + lines))
1635                 goto alloc_error;
1636
1637         while ((line = fgets(buffer, sizeof(buffer), view->pipe))) {
1638                 int linelen = strlen(line);
1639
1640                 if (linelen)
1641                         line[linelen - 1] = 0;
1642
1643                 if (!view->ops->read(view, line))
1644                         goto alloc_error;
1645
1646                 if (lines-- == 1)
1647                         break;
1648         }
1649
1650         {
1651                 int digits;
1652
1653                 lines = view->lines;
1654                 for (digits = 0; lines; digits++)
1655                         lines /= 10;
1656
1657                 /* Keep the displayed view in sync with line number scaling. */
1658                 if (digits != view->digits) {
1659                         view->digits = digits;
1660                         redraw_from = 0;
1661                 }
1662         }
1663
1664         if (redraw_from >= 0) {
1665                 /* If this is an incremental update, redraw the previous line
1666                  * since for commits some members could have changed when
1667                  * loading the main view. */
1668                 if (redraw_from > 0)
1669                         redraw_from--;
1670
1671                 /* Incrementally draw avoids flickering. */
1672                 redraw_view_from(view, redraw_from);
1673         }
1674
1675         /* Update the title _after_ the redraw so that if the redraw picks up a
1676          * commit reference in view->ref it'll be available here. */
1677         update_view_title(view);
1678
1679         if (ferror(view->pipe)) {
1680                 report("Failed to read: %s", strerror(errno));
1681                 goto end;
1682
1683         } else if (feof(view->pipe)) {
1684                 report("");
1685                 goto end;
1686         }
1687
1688         return TRUE;
1689
1690 alloc_error:
1691         report("Allocation failure");
1692
1693 end:
1694         end_update(view);
1695         return FALSE;
1696 }
1697
1698
1699 /*
1700  * View opening
1701  */
1702
1703 static void open_help_view(struct view *view)
1704 {
1705         char buf[BUFSIZ];
1706         int lines = ARRAY_SIZE(req_info) + 2;
1707         int i;
1708
1709         if (view->lines > 0)
1710                 return;
1711
1712         for (i = 0; i < ARRAY_SIZE(req_info); i++)
1713                 if (!req_info[i].request)
1714                         lines++;
1715
1716         view->line = calloc(lines, sizeof(*view->line));
1717         if (!view->line) {
1718                 report("Allocation failure");
1719                 return;
1720         }
1721
1722         view->ops->read(view, "Quick reference for tig keybindings:");
1723
1724         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
1725                 char *key;
1726
1727                 if (!req_info[i].request) {
1728                         view->ops->read(view, "");
1729                         view->ops->read(view, req_info[i].help);
1730                         continue;
1731                 }
1732
1733                 key = get_key(req_info[i].request);
1734                 if (!string_format(buf, "%-25s %s", key, req_info[i].help))
1735                         continue;
1736
1737                 view->ops->read(view, buf);
1738         }
1739 }
1740
1741 enum open_flags {
1742         OPEN_DEFAULT = 0,       /* Use default view switching. */
1743         OPEN_SPLIT = 1,         /* Split current view. */
1744         OPEN_BACKGROUNDED = 2,  /* Backgrounded. */
1745         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
1746 };
1747
1748 static void
1749 open_view(struct view *prev, enum request request, enum open_flags flags)
1750 {
1751         bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
1752         bool split = !!(flags & OPEN_SPLIT);
1753         bool reload = !!(flags & OPEN_RELOAD);
1754         struct view *view = VIEW(request);
1755         int nviews = displayed_views();
1756         struct view *base_view = display[0];
1757
1758         if (view == prev && nviews == 1 && !reload) {
1759                 report("Already in %s view", view->name);
1760                 return;
1761         }
1762
1763         if (view == VIEW(REQ_VIEW_HELP)) {
1764                 open_help_view(view);
1765
1766         } else if ((reload || strcmp(view->vid, view->id)) &&
1767                    !begin_update(view)) {
1768                 report("Failed to load %s view", view->name);
1769                 return;
1770         }
1771
1772         if (split) {
1773                 display[1] = view;
1774                 if (!backgrounded)
1775                         current_view = 1;
1776         } else {
1777                 /* Maximize the current view. */
1778                 memset(display, 0, sizeof(display));
1779                 current_view = 0;
1780                 display[current_view] = view;
1781         }
1782
1783         /* Resize the view when switching between split- and full-screen,
1784          * or when switching between two different full-screen views. */
1785         if (nviews != displayed_views() ||
1786             (nviews == 1 && base_view != display[0]))
1787                 resize_display();
1788
1789         if (split && prev->lineno - prev->offset >= prev->height) {
1790                 /* Take the title line into account. */
1791                 int lines = prev->lineno - prev->offset - prev->height + 1;
1792
1793                 /* Scroll the view that was split if the current line is
1794                  * outside the new limited view. */
1795                 do_scroll_view(prev, lines, TRUE);
1796         }
1797
1798         if (prev && view != prev) {
1799                 if (split && !backgrounded) {
1800                         /* "Blur" the previous view. */
1801                         update_view_title(prev);
1802                 }
1803
1804                 view->parent = prev;
1805         }
1806
1807         if (view->pipe && view->lines == 0) {
1808                 /* Clear the old view and let the incremental updating refill
1809                  * the screen. */
1810                 wclear(view->win);
1811                 report("");
1812         } else {
1813                 redraw_view(view);
1814                 report("");
1815         }
1816
1817         /* If the view is backgrounded the above calls to report()
1818          * won't redraw the view title. */
1819         if (backgrounded)
1820                 update_view_title(view);
1821 }
1822
1823
1824 /*
1825  * User request switch noodle
1826  */
1827
1828 static int
1829 view_driver(struct view *view, enum request request)
1830 {
1831         int i;
1832
1833         switch (request) {
1834         case REQ_MOVE_UP:
1835         case REQ_MOVE_DOWN:
1836         case REQ_MOVE_PAGE_UP:
1837         case REQ_MOVE_PAGE_DOWN:
1838         case REQ_MOVE_FIRST_LINE:
1839         case REQ_MOVE_LAST_LINE:
1840                 move_view(view, request, TRUE);
1841                 break;
1842
1843         case REQ_SCROLL_LINE_DOWN:
1844         case REQ_SCROLL_LINE_UP:
1845         case REQ_SCROLL_PAGE_DOWN:
1846         case REQ_SCROLL_PAGE_UP:
1847                 scroll_view(view, request);
1848                 break;
1849
1850         case REQ_VIEW_MAIN:
1851         case REQ_VIEW_DIFF:
1852         case REQ_VIEW_LOG:
1853         case REQ_VIEW_HELP:
1854         case REQ_VIEW_PAGER:
1855                 open_view(view, request, OPEN_DEFAULT);
1856                 break;
1857
1858         case REQ_NEXT:
1859         case REQ_PREVIOUS:
1860                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
1861
1862                 if (view == VIEW(REQ_VIEW_DIFF) &&
1863                     view->parent == VIEW(REQ_VIEW_MAIN)) {
1864                         bool redraw = display[1] == view;
1865
1866                         view = view->parent;
1867                         move_view(view, request, redraw);
1868                         if (redraw)
1869                                 update_view_title(view);
1870                 } else {
1871                         move_view(view, request, TRUE);
1872                         break;
1873                 }
1874                 /* Fall-through */
1875
1876         case REQ_ENTER:
1877                 if (!view->lines) {
1878                         report("Nothing to enter");
1879                         break;
1880                 }
1881                 return view->ops->enter(view, &view->line[view->lineno]);
1882
1883         case REQ_VIEW_NEXT:
1884         {
1885                 int nviews = displayed_views();
1886                 int next_view = (current_view + 1) % nviews;
1887
1888                 if (next_view == current_view) {
1889                         report("Only one view is displayed");
1890                         break;
1891                 }
1892
1893                 current_view = next_view;
1894                 /* Blur out the title of the previous view. */
1895                 update_view_title(view);
1896                 report("");
1897                 break;
1898         }
1899         case REQ_TOGGLE_LINENO:
1900                 opt_line_number = !opt_line_number;
1901                 redraw_display();
1902                 break;
1903
1904         case REQ_TOGGLE_REV_GRAPH:
1905                 opt_rev_graph = !opt_rev_graph;
1906                 redraw_display();
1907                 break;
1908
1909         case REQ_PROMPT:
1910                 /* Always reload^Wrerun commands from the prompt. */
1911                 open_view(view, opt_request, OPEN_RELOAD);
1912                 break;
1913
1914         case REQ_STOP_LOADING:
1915                 for (i = 0; i < ARRAY_SIZE(views); i++) {
1916                         view = &views[i];
1917                         if (view->pipe)
1918                                 report("Stopped loading the %s view", view->name),
1919                         end_update(view);
1920                 }
1921                 break;
1922
1923         case REQ_SHOW_VERSION:
1924                 report("%s (built %s)", VERSION, __DATE__);
1925                 return TRUE;
1926
1927         case REQ_SCREEN_RESIZE:
1928                 resize_display();
1929                 /* Fall-through */
1930         case REQ_SCREEN_REDRAW:
1931                 redraw_display();
1932                 break;
1933
1934         case REQ_SCREEN_UPDATE:
1935                 doupdate();
1936                 return TRUE;
1937
1938         case REQ_VIEW_CLOSE:
1939                 /* XXX: Mark closed views by letting view->parent point to the
1940                  * view itself. Parents to closed view should never be
1941                  * followed. */
1942                 if (view->parent &&
1943                     view->parent->parent != view->parent) {
1944                         memset(display, 0, sizeof(display));
1945                         current_view = 0;
1946                         display[current_view] = view->parent;
1947                         view->parent = view;
1948                         resize_display();
1949                         redraw_display();
1950                         break;
1951                 }
1952                 /* Fall-through */
1953         case REQ_QUIT:
1954                 return FALSE;
1955
1956         default:
1957                 /* An unknown key will show most commonly used commands. */
1958                 report("Unknown key, press 'h' for help");
1959                 return TRUE;
1960         }
1961
1962         return TRUE;
1963 }
1964
1965
1966 /*
1967  * Pager backend
1968  */
1969
1970 static bool
1971 pager_draw(struct view *view, struct line *line, unsigned int lineno)
1972 {
1973         char *text = line->data;
1974         enum line_type type = line->type;
1975         int textlen = strlen(text);
1976         int attr;
1977
1978         wmove(view->win, lineno, 0);
1979
1980         if (view->offset + lineno == view->lineno) {
1981                 if (type == LINE_COMMIT) {
1982                         string_copy(view->ref, text + 7);
1983                         string_copy(ref_commit, view->ref);
1984                 }
1985
1986                 type = LINE_CURSOR;
1987                 wchgat(view->win, -1, 0, type, NULL);
1988         }
1989
1990         attr = get_line_attr(type);
1991         wattrset(view->win, attr);
1992
1993         if (opt_line_number || opt_tab_size < TABSIZE) {
1994                 static char spaces[] = "                    ";
1995                 int col_offset = 0, col = 0;
1996
1997                 if (opt_line_number) {
1998                         unsigned long real_lineno = view->offset + lineno + 1;
1999
2000                         if (real_lineno == 1 ||
2001                             (real_lineno % opt_num_interval) == 0) {
2002                                 wprintw(view->win, "%.*d", view->digits, real_lineno);
2003
2004                         } else {
2005                                 waddnstr(view->win, spaces,
2006                                          MIN(view->digits, STRING_SIZE(spaces)));
2007                         }
2008                         waddstr(view->win, ": ");
2009                         col_offset = view->digits + 2;
2010                 }
2011
2012                 while (text && col_offset + col < view->width) {
2013                         int cols_max = view->width - col_offset - col;
2014                         char *pos = text;
2015                         int cols;
2016
2017                         if (*text == '\t') {
2018                                 text++;
2019                                 assert(sizeof(spaces) > TABSIZE);
2020                                 pos = spaces;
2021                                 cols = opt_tab_size - (col % opt_tab_size);
2022
2023                         } else {
2024                                 text = strchr(text, '\t');
2025                                 cols = line ? text - pos : strlen(pos);
2026                         }
2027
2028                         waddnstr(view->win, pos, MIN(cols, cols_max));
2029                         col += cols;
2030                 }
2031
2032         } else {
2033                 int col = 0, pos = 0;
2034
2035                 for (; pos < textlen && col < view->width; pos++, col++)
2036                         if (text[pos] == '\t')
2037                                 col += TABSIZE - (col % TABSIZE) - 1;
2038
2039                 waddnstr(view->win, text, pos);
2040         }
2041
2042         return TRUE;
2043 }
2044
2045 static void
2046 add_pager_refs(struct view *view, struct line *line)
2047 {
2048         char buf[1024];
2049         char *data = line->data;
2050         struct ref **refs;
2051         int bufpos = 0, refpos = 0;
2052         const char *sep = "Refs: ";
2053
2054         assert(line->type == LINE_COMMIT);
2055
2056         refs = get_refs(data + STRING_SIZE("commit "));
2057         if (!refs)
2058                 return;
2059
2060         do {
2061                 struct ref *ref = refs[refpos];
2062                 char *fmt = ref->tag ? "%s[%s]" : "%s%s";
2063
2064                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
2065                         return;
2066                 sep = ", ";
2067         } while (refs[refpos++]->next);
2068
2069         if (!realloc_lines(view, view->line_size + 1))
2070                 return;
2071
2072         line = &view->line[view->lines];
2073         line->data = strdup(buf);
2074         if (!line->data)
2075                 return;
2076
2077         line->type = LINE_PP_REFS;
2078         view->lines++;
2079 }
2080
2081 static bool
2082 pager_read(struct view *view, char *data)
2083 {
2084         struct line *line = &view->line[view->lines];
2085
2086         line->data = strdup(data);
2087         if (!line->data)
2088                 return FALSE;
2089
2090         line->type = get_line_type(line->data);
2091         view->lines++;
2092
2093         if (line->type == LINE_COMMIT &&
2094             (view == VIEW(REQ_VIEW_DIFF) ||
2095              view == VIEW(REQ_VIEW_LOG)))
2096                 add_pager_refs(view, line);
2097
2098         return TRUE;
2099 }
2100
2101 static bool
2102 pager_enter(struct view *view, struct line *line)
2103 {
2104         int split = 0;
2105
2106         if (line->type == LINE_COMMIT &&
2107            (view == VIEW(REQ_VIEW_LOG) ||
2108             view == VIEW(REQ_VIEW_PAGER))) {
2109                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
2110                 split = 1;
2111         }
2112
2113         /* Always scroll the view even if it was split. That way
2114          * you can use Enter to scroll through the log view and
2115          * split open each commit diff. */
2116         scroll_view(view, REQ_SCROLL_LINE_DOWN);
2117
2118         /* FIXME: A minor workaround. Scrolling the view will call report("")
2119          * but if we are scrolling a non-current view this won't properly
2120          * update the view title. */
2121         if (split)
2122                 update_view_title(view);
2123
2124         return TRUE;
2125 }
2126
2127 static struct view_ops pager_ops = {
2128         "line",
2129         pager_draw,
2130         pager_read,
2131         pager_enter,
2132 };
2133
2134
2135 /*
2136  * Main view backend
2137  */
2138
2139 struct commit {
2140         char id[41];                    /* SHA1 ID. */
2141         char title[75];                 /* First line of the commit message. */
2142         char author[75];                /* Author of the commit. */
2143         struct tm time;                 /* Date from the author ident. */
2144         struct ref **refs;              /* Repository references. */
2145         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
2146         size_t graph_size;              /* The width of the graph array. */
2147 };
2148
2149 static bool
2150 main_draw(struct view *view, struct line *line, unsigned int lineno)
2151 {
2152         char buf[DATE_COLS + 1];
2153         struct commit *commit = line->data;
2154         enum line_type type;
2155         int col = 0;
2156         size_t timelen;
2157         size_t authorlen;
2158         int trimmed = 1;
2159
2160         if (!*commit->author)
2161                 return FALSE;
2162
2163         wmove(view->win, lineno, col);
2164
2165         if (view->offset + lineno == view->lineno) {
2166                 string_copy(view->ref, commit->id);
2167                 string_copy(ref_commit, view->ref);
2168                 type = LINE_CURSOR;
2169                 wattrset(view->win, get_line_attr(type));
2170                 wchgat(view->win, -1, 0, type, NULL);
2171
2172         } else {
2173                 type = LINE_MAIN_COMMIT;
2174                 wattrset(view->win, get_line_attr(LINE_MAIN_DATE));
2175         }
2176
2177         timelen = strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time);
2178         waddnstr(view->win, buf, timelen);
2179         waddstr(view->win, " ");
2180
2181         col += DATE_COLS;
2182         wmove(view->win, lineno, col);
2183         if (type != LINE_CURSOR)
2184                 wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR));
2185
2186         if (opt_utf8) {
2187                 authorlen = utf8_length(commit->author, AUTHOR_COLS - 2, &col, &trimmed);
2188         } else {
2189                 authorlen = strlen(commit->author);
2190                 if (authorlen > AUTHOR_COLS - 2) {
2191                         authorlen = AUTHOR_COLS - 2;
2192                         trimmed = 1;
2193                 }
2194         }
2195
2196         if (trimmed) {
2197                 waddnstr(view->win, commit->author, authorlen);
2198                 if (type != LINE_CURSOR)
2199                         wattrset(view->win, get_line_attr(LINE_MAIN_DELIM));
2200                 waddch(view->win, '~');
2201         } else {
2202                 waddstr(view->win, commit->author);
2203         }
2204
2205         col += AUTHOR_COLS;
2206         if (type != LINE_CURSOR)
2207                 wattrset(view->win, A_NORMAL);
2208
2209         if (opt_rev_graph && commit->graph_size) {
2210                 size_t i;
2211
2212                 wmove(view->win, lineno, col);
2213                 /* Using waddch() instead of waddnstr() ensures that
2214                  * they'll be rendered correctly for the cursor line. */
2215                 for (i = 0; i < commit->graph_size; i++)
2216                         waddch(view->win, commit->graph[i]);
2217
2218                 col += commit->graph_size + 1;
2219         }
2220
2221         wmove(view->win, lineno, col);
2222
2223         if (commit->refs) {
2224                 size_t i = 0;
2225
2226                 do {
2227                         if (type == LINE_CURSOR)
2228                                 ;
2229                         else if (commit->refs[i]->tag)
2230                                 wattrset(view->win, get_line_attr(LINE_MAIN_TAG));
2231                         else
2232                                 wattrset(view->win, get_line_attr(LINE_MAIN_REF));
2233                         waddstr(view->win, "[");
2234                         waddstr(view->win, commit->refs[i]->name);
2235                         waddstr(view->win, "]");
2236                         if (type != LINE_CURSOR)
2237                                 wattrset(view->win, A_NORMAL);
2238                         waddstr(view->win, " ");
2239                         col += strlen(commit->refs[i]->name) + STRING_SIZE("[] ");
2240                 } while (commit->refs[i++]->next);
2241         }
2242
2243         if (type != LINE_CURSOR)
2244                 wattrset(view->win, get_line_attr(type));
2245
2246         {
2247                 int titlelen = strlen(commit->title);
2248
2249                 if (col + titlelen > view->width)
2250                         titlelen = view->width - col;
2251
2252                 waddnstr(view->win, commit->title, titlelen);
2253         }
2254
2255         return TRUE;
2256 }
2257
2258 /* Reads git log --pretty=raw output and parses it into the commit struct. */
2259 static bool
2260 main_read(struct view *view, char *line)
2261 {
2262         enum line_type type = get_line_type(line);
2263         struct commit *commit = view->lines
2264                               ? view->line[view->lines - 1].data : NULL;
2265
2266         switch (type) {
2267         case LINE_COMMIT:
2268                 commit = calloc(1, sizeof(struct commit));
2269                 if (!commit)
2270                         return FALSE;
2271
2272                 line += STRING_SIZE("commit ");
2273
2274                 view->line[view->lines++].data = commit;
2275                 string_copy(commit->id, line);
2276                 commit->refs = get_refs(commit->id);
2277                 commit->graph[commit->graph_size++] = ACS_LTEE;
2278                 break;
2279
2280         case LINE_AUTHOR:
2281         {
2282                 char *ident = line + STRING_SIZE("author ");
2283                 char *end = strchr(ident, '<');
2284
2285                 if (!commit)
2286                         break;
2287
2288                 if (end) {
2289                         for (; end > ident && isspace(end[-1]); end--) ;
2290                         *end = 0;
2291                 }
2292
2293                 string_copy(commit->author, ident);
2294
2295                 /* Parse epoch and timezone */
2296                 if (end) {
2297                         char *secs = strchr(end + 1, '>');
2298                         char *zone;
2299                         time_t time;
2300
2301                         if (!secs || secs[1] != ' ')
2302                                 break;
2303
2304                         secs += 2;
2305                         time = (time_t) atol(secs);
2306                         zone = strchr(secs, ' ');
2307                         if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
2308                                 long tz;
2309
2310                                 zone++;
2311                                 tz  = ('0' - zone[1]) * 60 * 60 * 10;
2312                                 tz += ('0' - zone[2]) * 60 * 60;
2313                                 tz += ('0' - zone[3]) * 60;
2314                                 tz += ('0' - zone[4]) * 60;
2315
2316                                 if (zone[0] == '-')
2317                                         tz = -tz;
2318
2319                                 time -= tz;
2320                         }
2321                         gmtime_r(&time, &commit->time);
2322                 }
2323                 break;
2324         }
2325         default:
2326                 if (!commit)
2327                         break;
2328
2329                 /* Fill in the commit title if it has not already been set. */
2330                 if (commit->title[0])
2331                         break;
2332
2333                 /* Require titles to start with a non-space character at the
2334                  * offset used by git log. */
2335                 /* FIXME: More gracefull handling of titles; append "..." to
2336                  * shortened titles, etc. */
2337                 if (strncmp(line, "    ", 4) ||
2338                     isspace(line[4]))
2339                         break;
2340
2341                 string_copy(commit->title, line + 4);
2342         }
2343
2344         return TRUE;
2345 }
2346
2347 static bool
2348 main_enter(struct view *view, struct line *line)
2349 {
2350         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
2351
2352         open_view(view, REQ_VIEW_DIFF, flags);
2353         return TRUE;
2354 }
2355
2356 static struct view_ops main_ops = {
2357         "commit",
2358         main_draw,
2359         main_read,
2360         main_enter,
2361 };
2362
2363
2364 /*
2365  * Unicode / UTF-8 handling
2366  *
2367  * NOTE: Much of the following code for dealing with unicode is derived from
2368  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
2369  * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
2370  */
2371
2372 /* I've (over)annotated a lot of code snippets because I am not entirely
2373  * confident that the approach taken by this small UTF-8 interface is correct.
2374  * --jonas */
2375
2376 static inline int
2377 unicode_width(unsigned long c)
2378 {
2379         if (c >= 0x1100 &&
2380            (c <= 0x115f                         /* Hangul Jamo */
2381             || c == 0x2329
2382             || c == 0x232a
2383             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
2384                                                 /* CJK ... Yi */
2385             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
2386             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
2387             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
2388             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
2389             || (c >= 0xffe0  && c <= 0xffe6)
2390             || (c >= 0x20000 && c <= 0x2fffd)
2391             || (c >= 0x30000 && c <= 0x3fffd)))
2392                 return 2;
2393
2394         return 1;
2395 }
2396
2397 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
2398  * Illegal bytes are set one. */
2399 static const unsigned char utf8_bytes[256] = {
2400         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,
2401         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,
2402         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,
2403         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,
2404         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,
2405         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,
2406         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,
2407         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,
2408 };
2409
2410 /* Decode UTF-8 multi-byte representation into a unicode character. */
2411 static inline unsigned long
2412 utf8_to_unicode(const char *string, size_t length)
2413 {
2414         unsigned long unicode;
2415
2416         switch (length) {
2417         case 1:
2418                 unicode  =   string[0];
2419                 break;
2420         case 2:
2421                 unicode  =  (string[0] & 0x1f) << 6;
2422                 unicode +=  (string[1] & 0x3f);
2423                 break;
2424         case 3:
2425                 unicode  =  (string[0] & 0x0f) << 12;
2426                 unicode += ((string[1] & 0x3f) << 6);
2427                 unicode +=  (string[2] & 0x3f);
2428                 break;
2429         case 4:
2430                 unicode  =  (string[0] & 0x0f) << 18;
2431                 unicode += ((string[1] & 0x3f) << 12);
2432                 unicode += ((string[2] & 0x3f) << 6);
2433                 unicode +=  (string[3] & 0x3f);
2434                 break;
2435         case 5:
2436                 unicode  =  (string[0] & 0x0f) << 24;
2437                 unicode += ((string[1] & 0x3f) << 18);
2438                 unicode += ((string[2] & 0x3f) << 12);
2439                 unicode += ((string[3] & 0x3f) << 6);
2440                 unicode +=  (string[4] & 0x3f);
2441                 break;
2442         case 6:
2443                 unicode  =  (string[0] & 0x01) << 30;
2444                 unicode += ((string[1] & 0x3f) << 24);
2445                 unicode += ((string[2] & 0x3f) << 18);
2446                 unicode += ((string[3] & 0x3f) << 12);
2447                 unicode += ((string[4] & 0x3f) << 6);
2448                 unicode +=  (string[5] & 0x3f);
2449                 break;
2450         default:
2451                 die("Invalid unicode length");
2452         }
2453
2454         /* Invalid characters could return the special 0xfffd value but NUL
2455          * should be just as good. */
2456         return unicode > 0xffff ? 0 : unicode;
2457 }
2458
2459 /* Calculates how much of string can be shown within the given maximum width
2460  * and sets trimmed parameter to non-zero value if all of string could not be
2461  * shown.
2462  *
2463  * Additionally, adds to coloffset how many many columns to move to align with
2464  * the expected position. Takes into account how multi-byte and double-width
2465  * characters will effect the cursor position.
2466  *
2467  * Returns the number of bytes to output from string to satisfy max_width. */
2468 static size_t
2469 utf8_length(const char *string, size_t max_width, int *coloffset, int *trimmed)
2470 {
2471         const char *start = string;
2472         const char *end = strchr(string, '\0');
2473         size_t mbwidth = 0;
2474         size_t width = 0;
2475
2476         *trimmed = 0;
2477
2478         while (string < end) {
2479                 int c = *(unsigned char *) string;
2480                 unsigned char bytes = utf8_bytes[c];
2481                 size_t ucwidth;
2482                 unsigned long unicode;
2483
2484                 if (string + bytes > end)
2485                         break;
2486
2487                 /* Change representation to figure out whether
2488                  * it is a single- or double-width character. */
2489
2490                 unicode = utf8_to_unicode(string, bytes);
2491                 /* FIXME: Graceful handling of invalid unicode character. */
2492                 if (!unicode)
2493                         break;
2494
2495                 ucwidth = unicode_width(unicode);
2496                 width  += ucwidth;
2497                 if (width > max_width) {
2498                         *trimmed = 1;
2499                         break;
2500                 }
2501
2502                 /* The column offset collects the differences between the
2503                  * number of bytes encoding a character and the number of
2504                  * columns will be used for rendering said character.
2505                  *
2506                  * So if some character A is encoded in 2 bytes, but will be
2507                  * represented on the screen using only 1 byte this will and up
2508                  * adding 1 to the multi-byte column offset.
2509                  *
2510                  * Assumes that no double-width character can be encoding in
2511                  * less than two bytes. */
2512                 if (bytes > ucwidth)
2513                         mbwidth += bytes - ucwidth;
2514
2515                 string  += bytes;
2516         }
2517
2518         *coloffset += mbwidth;
2519
2520         return string - start;
2521 }
2522
2523
2524 /*
2525  * Status management
2526  */
2527
2528 /* Whether or not the curses interface has been initialized. */
2529 static bool cursed = FALSE;
2530
2531 /* The status window is used for polling keystrokes. */
2532 static WINDOW *status_win;
2533
2534 /* Update status and title window. */
2535 static void
2536 report(const char *msg, ...)
2537 {
2538         static bool empty = TRUE;
2539         struct view *view = display[current_view];
2540
2541         if (!empty || *msg) {
2542                 va_list args;
2543
2544                 va_start(args, msg);
2545
2546                 werase(status_win);
2547                 wmove(status_win, 0, 0);
2548                 if (*msg) {
2549                         vwprintw(status_win, msg, args);
2550                         empty = FALSE;
2551                 } else {
2552                         empty = TRUE;
2553                 }
2554                 wrefresh(status_win);
2555
2556                 va_end(args);
2557         }
2558
2559         update_view_title(view);
2560         update_display_cursor();
2561 }
2562
2563 /* Controls when nodelay should be in effect when polling user input. */
2564 static void
2565 set_nonblocking_input(bool loading)
2566 {
2567         static unsigned int loading_views;
2568
2569         if ((loading == FALSE && loading_views-- == 1) ||
2570             (loading == TRUE  && loading_views++ == 0))
2571                 nodelay(status_win, loading);
2572 }
2573
2574 static void
2575 init_display(void)
2576 {
2577         int x, y;
2578
2579         /* Initialize the curses library */
2580         if (isatty(STDIN_FILENO)) {
2581                 cursed = !!initscr();
2582         } else {
2583                 /* Leave stdin and stdout alone when acting as a pager. */
2584                 FILE *io = fopen("/dev/tty", "r+");
2585
2586                 cursed = !!newterm(NULL, io, io);
2587         }
2588
2589         if (!cursed)
2590                 die("Failed to initialize curses");
2591
2592         nonl();         /* Tell curses not to do NL->CR/NL on output */
2593         cbreak();       /* Take input chars one at a time, no wait for \n */
2594         noecho();       /* Don't echo input */
2595         leaveok(stdscr, TRUE);
2596
2597         if (has_colors())
2598                 init_colors();
2599
2600         getmaxyx(stdscr, y, x);
2601         status_win = newwin(1, 0, y - 1, 0);
2602         if (!status_win)
2603                 die("Failed to create status window");
2604
2605         /* Enable keyboard mapping */
2606         keypad(status_win, TRUE);
2607         wbkgdset(status_win, get_line_attr(LINE_STATUS));
2608 }
2609
2610 static int
2611 read_prompt(void)
2612 {
2613         enum { READING, STOP, CANCEL } status = READING;
2614         char buf[sizeof(opt_cmd) - STRING_SIZE("git \0")];
2615         int pos = 0;
2616
2617         while (status == READING) {
2618                 struct view *view;
2619                 int i, key;
2620
2621                 foreach_view (view, i)
2622                         update_view(view);
2623
2624                 report(":%.*s", pos, buf);
2625                 /* Refresh, accept single keystroke of input */
2626                 key = wgetch(status_win);
2627                 switch (key) {
2628                 case KEY_RETURN:
2629                 case KEY_ENTER:
2630                 case '\n':
2631                         status = pos ? STOP : CANCEL;
2632                         break;
2633
2634                 case KEY_BACKSPACE:
2635                         if (pos > 0)
2636                                 pos--;
2637                         else
2638                                 status = CANCEL;
2639                         break;
2640
2641                 case KEY_ESC:
2642                         status = CANCEL;
2643                         break;
2644
2645                 case ERR:
2646                         break;
2647
2648                 default:
2649                         if (pos >= sizeof(buf)) {
2650                                 report("Input string too long");
2651                                 return ERR;
2652                         }
2653
2654                         if (isprint(key))
2655                                 buf[pos++] = (char) key;
2656                 }
2657         }
2658
2659         if (status == CANCEL) {
2660                 /* Clear the status window */
2661                 report("");
2662                 return ERR;
2663         }
2664
2665         buf[pos++] = 0;
2666         if (!string_format(opt_cmd, "git %s", buf))
2667                 return ERR;
2668         opt_request = REQ_VIEW_PAGER;
2669
2670         return OK;
2671 }
2672
2673 /*
2674  * Repository references
2675  */
2676
2677 static struct ref *refs;
2678 static size_t refs_size;
2679
2680 /* Id <-> ref store */
2681 static struct ref ***id_refs;
2682 static size_t id_refs_size;
2683
2684 static struct ref **
2685 get_refs(char *id)
2686 {
2687         struct ref ***tmp_id_refs;
2688         struct ref **ref_list = NULL;
2689         size_t ref_list_size = 0;
2690         size_t i;
2691
2692         for (i = 0; i < id_refs_size; i++)
2693                 if (!strcmp(id, id_refs[i][0]->id))
2694                         return id_refs[i];
2695
2696         tmp_id_refs = realloc(id_refs, (id_refs_size + 1) * sizeof(*id_refs));
2697         if (!tmp_id_refs)
2698                 return NULL;
2699
2700         id_refs = tmp_id_refs;
2701
2702         for (i = 0; i < refs_size; i++) {
2703                 struct ref **tmp;
2704
2705                 if (strcmp(id, refs[i].id))
2706                         continue;
2707
2708                 tmp = realloc(ref_list, (ref_list_size + 1) * sizeof(*ref_list));
2709                 if (!tmp) {
2710                         if (ref_list)
2711                                 free(ref_list);
2712                         return NULL;
2713                 }
2714
2715                 ref_list = tmp;
2716                 if (ref_list_size > 0)
2717                         ref_list[ref_list_size - 1]->next = 1;
2718                 ref_list[ref_list_size] = &refs[i];
2719
2720                 /* XXX: The properties of the commit chains ensures that we can
2721                  * safely modify the shared ref. The repo references will
2722                  * always be similar for the same id. */
2723                 ref_list[ref_list_size]->next = 0;
2724                 ref_list_size++;
2725         }
2726
2727         if (ref_list)
2728                 id_refs[id_refs_size++] = ref_list;
2729
2730         return ref_list;
2731 }
2732
2733 static int
2734 read_ref(char *id, int idlen, char *name, int namelen)
2735 {
2736         struct ref *ref;
2737         bool tag = FALSE;
2738
2739         if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
2740                 /* Commits referenced by tags has "^{}" appended. */
2741                 if (name[namelen - 1] != '}')
2742                         return OK;
2743
2744                 while (namelen > 0 && name[namelen] != '^')
2745                         namelen--;
2746
2747                 tag = TRUE;
2748                 namelen -= STRING_SIZE("refs/tags/");
2749                 name    += STRING_SIZE("refs/tags/");
2750
2751         } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
2752                 namelen -= STRING_SIZE("refs/heads/");
2753                 name    += STRING_SIZE("refs/heads/");
2754
2755         } else if (!strcmp(name, "HEAD")) {
2756                 return OK;
2757         }
2758
2759         refs = realloc(refs, sizeof(*refs) * (refs_size + 1));
2760         if (!refs)
2761                 return ERR;
2762
2763         ref = &refs[refs_size++];
2764         ref->name = malloc(namelen + 1);
2765         if (!ref->name)
2766                 return ERR;
2767
2768         strncpy(ref->name, name, namelen);
2769         ref->name[namelen] = 0;
2770         ref->tag = tag;
2771         string_copy(ref->id, id);
2772
2773         return OK;
2774 }
2775
2776 static int
2777 load_refs(void)
2778 {
2779         const char *cmd_env = getenv("TIG_LS_REMOTE");
2780         const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
2781
2782         return read_properties(popen(cmd, "r"), "\t", read_ref);
2783 }
2784
2785 static int
2786 read_repo_config_option(char *name, int namelen, char *value, int valuelen)
2787 {
2788         if (!strcmp(name, "i18n.commitencoding"))
2789                 string_copy(opt_encoding, value);
2790
2791         return OK;
2792 }
2793
2794 static int
2795 load_repo_config(void)
2796 {
2797         return read_properties(popen("git repo-config --list", "r"),
2798                                "=", read_repo_config_option);
2799 }
2800
2801 static int
2802 read_properties(FILE *pipe, const char *separators,
2803                 int (*read_property)(char *, int, char *, int))
2804 {
2805         char buffer[BUFSIZ];
2806         char *name;
2807         int state = OK;
2808
2809         if (!pipe)
2810                 return ERR;
2811
2812         while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
2813                 char *value;
2814                 size_t namelen;
2815                 size_t valuelen;
2816
2817                 name = chomp_string(name);
2818                 namelen = strcspn(name, separators);
2819
2820                 if (name[namelen]) {
2821                         name[namelen] = 0;
2822                         value = chomp_string(name + namelen + 1);
2823                         valuelen = strlen(value);
2824
2825                 } else {
2826                         value = "";
2827                         valuelen = 0;
2828                 }
2829
2830                 state = read_property(name, namelen, value, valuelen);
2831         }
2832
2833         if (state != ERR && ferror(pipe))
2834                 state = ERR;
2835
2836         pclose(pipe);
2837
2838         return state;
2839 }
2840
2841
2842 /*
2843  * Main
2844  */
2845
2846 static void __NORETURN
2847 quit(int sig)
2848 {
2849         /* XXX: Restore tty modes and let the OS cleanup the rest! */
2850         if (cursed)
2851                 endwin();
2852         exit(0);
2853 }
2854
2855 static void __NORETURN
2856 die(const char *err, ...)
2857 {
2858         va_list args;
2859
2860         endwin();
2861
2862         va_start(args, err);
2863         fputs("tig: ", stderr);
2864         vfprintf(stderr, err, args);
2865         fputs("\n", stderr);
2866         va_end(args);
2867
2868         exit(1);
2869 }
2870
2871 int
2872 main(int argc, char *argv[])
2873 {
2874         struct view *view;
2875         enum request request;
2876         size_t i;
2877
2878         signal(SIGINT, quit);
2879
2880         if (load_options() == ERR)
2881                 die("Failed to load user config.");
2882
2883         /* Load the repo config file so options can be overwritten from
2884          * the command line.  */
2885         if (load_repo_config() == ERR)
2886                 die("Failed to load repo config.");
2887
2888         if (!parse_options(argc, argv))
2889                 return 0;
2890
2891         if (load_refs() == ERR)
2892                 die("Failed to load refs.");
2893
2894         /* Require a git repository unless when running in pager mode. */
2895         if (refs_size == 0 && opt_request != REQ_VIEW_PAGER)
2896                 die("Not a git repository");
2897
2898         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2899                 view->cmd_env = getenv(view->cmd_env);
2900
2901         request = opt_request;
2902
2903         init_display();
2904
2905         while (view_driver(display[current_view], request)) {
2906                 int key;
2907                 int i;
2908
2909                 foreach_view (view, i)
2910                         update_view(view);
2911
2912                 /* Refresh, accept single keystroke of input */
2913                 key = wgetch(status_win);
2914
2915                 request = get_keybinding(display[current_view]->keymap, key);
2916
2917                 /* Some low-level request handling. This keeps access to
2918                  * status_win restricted. */
2919                 switch (request) {
2920                 case REQ_PROMPT:
2921                         if (read_prompt() == ERR)
2922                                 request = REQ_SCREEN_UPDATE;
2923                         break;
2924
2925                 case REQ_SCREEN_RESIZE:
2926                 {
2927                         int height, width;
2928
2929                         getmaxyx(stdscr, height, width);
2930
2931                         /* Resize the status view and let the view driver take
2932                          * care of resizing the displayed views. */
2933                         wresize(status_win, 1, width);
2934                         mvwin(status_win, height - 1, 0);
2935                         wrefresh(status_win);
2936                         break;
2937                 }
2938                 default:
2939                         break;
2940                 }
2941         }
2942
2943         quit(0);
2944
2945         return 0;
2946 }