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