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