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