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