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