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