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